From 1f33de9100788a56a02d1abbc28f15385329b068 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Queru Date: Thu, 12 Nov 2009 18:46:10 -0800 Subject: eclair snapshot --- Android.mk | 19 +- AndroidManifest.xml | 91 +- libwbxml/Android.mk | 16 +- libwbxml/include/imps_encoder.h | 18 +- libwbxml/include/wbxml_encoder.h | 40 + libwbxml/src/imps_encoder.cpp | 130 +- libwbxml/src/wbxml_encoder.cpp | 173 + plugin/Android.mk | 31 - .../com/android/im/plugin/BrandingResourceIDs.java | 32 +- plugin/com/android/im/plugin/IImPlugin.aidl | 46 - plugin/com/android/im/plugin/IPasswordDigest.aidl | 40 - plugin/com/android/im/plugin/IPresenceMapping.aidl | 83 - plugin/com/android/im/plugin/ImPlugin.java | 48 + .../com/android/im/plugin/ImPluginConstants.java | 2 +- plugin/com/android/im/plugin/PasswordDigest.java | 43 + plugin/com/android/im/plugin/PresenceMapping.java | 85 + res/color/landing_page_text.xml | 22 + res/color/landing_page_text_secondary.xml | 22 + res/drawable-hdpi/avatar_unknown.png | Bin 0 -> 1487 bytes res/drawable-hdpi/background_textfield.9.png | Bin 0 -> 482 bytes .../dashboard_highlight_selected.9.png | Bin 0 -> 2358 bytes .../dialog_im_switch_chats_strip.9.png | Bin 0 -> 218 bytes res/drawable-hdpi/droid_watermark.png | Bin 0 -> 1132 bytes res/drawable-hdpi/group_chat.png | Bin 0 -> 1017 bytes res/drawable-hdpi/group_chat_new.png | Bin 0 -> 1016 bytes res/drawable-hdpi/ic_im_block.png | Bin 0 -> 1975 bytes res/drawable-hdpi/ic_im_message_not_sent.png | Bin 0 -> 658 bytes res/drawable-hdpi/ic_launcher_im.png | Bin 0 -> 4675 bytes res/drawable-hdpi/ic_menu_my_profile.png | Bin 0 -> 2746 bytes res/drawable-hdpi/ic_menu_view_profile.png | Bin 0 -> 3616 bytes .../im_avatar_picture_border_normal.9.png | Bin 0 -> 578 bytes res/drawable-hdpi/im_logo.png | Bin 0 -> 7388 bytes res/drawable-hdpi/imlogo_s.png | Bin 0 -> 2706 bytes .../list_item_im_bubble_default.9.png | Bin 0 -> 1507 bytes .../list_item_im_bubble_pressed.9.png | Bin 0 -> 1673 bytes .../list_item_im_bubble_selected.9.png | Bin 0 -> 1719 bytes res/drawable-hdpi/picture_frame_background.9.png | Bin 0 -> 546 bytes res/drawable-hdpi/status_chat.png | Bin 0 -> 867 bytes res/drawable-hdpi/status_chat_new.png | Bin 0 -> 568 bytes res/drawable-hdpi/text_divider_horizontal.9.png | Bin 0 -> 543 bytes res/drawable-hdpi/textfield_im_pressed.9.png | Bin 0 -> 518 bytes res/drawable-hdpi/textfield_im_received.9.png | Bin 0 -> 275 bytes res/drawable-hdpi/textfield_im_selected.9.png | Bin 0 -> 514 bytes res/drawable-mdpi/avatar_unknown.png | Bin 0 -> 1719 bytes res/drawable-mdpi/background_textfield.9.png | Bin 0 -> 286 bytes .../dashboard_highlight_selected.9.png | Bin 0 -> 1226 bytes .../dialog_im_switch_chats_strip.9.png | Bin 0 -> 259 bytes res/drawable-mdpi/droid_watermark.png | Bin 0 -> 767 bytes res/drawable-mdpi/group_chat.png | Bin 0 -> 632 bytes res/drawable-mdpi/group_chat_new.png | Bin 0 -> 624 bytes res/drawable-mdpi/ic_im_block.png | Bin 0 -> 1331 bytes res/drawable-mdpi/ic_im_message_not_sent.png | Bin 0 -> 275 bytes res/drawable-mdpi/ic_launcher_im.png | Bin 0 -> 2951 bytes res/drawable-mdpi/ic_menu_my_profile.png | Bin 0 -> 1999 bytes res/drawable-mdpi/ic_menu_view_profile.png | Bin 0 -> 2232 bytes .../im_avatar_picture_border_normal.9.png | Bin 0 -> 390 bytes res/drawable-mdpi/im_logo.png | Bin 0 -> 5967 bytes res/drawable-mdpi/imlogo_s.png | Bin 0 -> 1912 bytes .../list_item_im_bubble_default.9.png | Bin 0 -> 1225 bytes .../list_item_im_bubble_pressed.9.png | Bin 0 -> 1186 bytes .../list_item_im_bubble_selected.9.png | Bin 0 -> 1029 bytes res/drawable-mdpi/picture_frame_background.9.png | Bin 0 -> 3225 bytes res/drawable-mdpi/status_chat.png | Bin 0 -> 616 bytes res/drawable-mdpi/status_chat_new.png | Bin 0 -> 614 bytes res/drawable-mdpi/text_divider_horizontal.9.png | Bin 0 -> 626 bytes res/drawable-mdpi/textfield_im_pressed.9.png | Bin 0 -> 315 bytes res/drawable-mdpi/textfield_im_received.9.png | Bin 0 -> 242 bytes res/drawable-mdpi/textfield_im_selected.9.png | Bin 0 -> 394 bytes res/drawable/avatar_unknown.png | Bin 1719 -> 0 bytes res/drawable/background_textfield.9.png | Bin 286 -> 0 bytes res/drawable/dashboard_highlight_selected.9.png | Bin 1226 -> 0 bytes res/drawable/default_background.9.png | Bin 0 -> 2893 bytes res/drawable/dialog_im_switch_chats_strip.9.png | Bin 259 -> 0 bytes res/drawable/droid_watermark.png | Bin 767 -> 0 bytes res/drawable/group_chat.png | Bin 632 -> 0 bytes res/drawable/group_chat_new.png | Bin 624 -> 0 bytes res/drawable/ic_im_block.png | Bin 1331 -> 0 bytes res/drawable/ic_im_message_not_sent.png | Bin 275 -> 0 bytes res/drawable/ic_launcher_im.png | Bin 2951 -> 0 bytes res/drawable/ic_menu_my_profile.png | Bin 1999 -> 0 bytes res/drawable/ic_menu_view_profile.png | Bin 2232 -> 0 bytes res/drawable/im_avatar_picture_border_normal.9.png | Bin 390 -> 0 bytes res/drawable/im_logo.png | Bin 5967 -> 0 bytes res/drawable/imlogo_s.png | Bin 1912 -> 0 bytes res/drawable/list_item_im_bubble_default.9.png | Bin 1225 -> 0 bytes res/drawable/list_item_im_bubble_pressed.9.png | Bin 1186 -> 0 bytes res/drawable/list_item_im_bubble_selected.9.png | Bin 1029 -> 0 bytes res/drawable/picture_frame_background.9.png | Bin 3225 -> 0 bytes res/drawable/status_chat.png | Bin 616 -> 0 bytes res/drawable/status_chat_new.png | Bin 614 -> 0 bytes res/drawable/text_divider_horizontal.9.png | Bin 626 -> 0 bytes res/drawable/textfield_im_pressed.9.png | Bin 315 -> 0 bytes res/drawable/textfield_im_received.9.png | Bin 242 -> 0 bytes res/drawable/textfield_im_selected.9.png | Bin 394 -> 0 bytes res/layout/account_view.xml | 91 + res/layout/contact_list_view.xml | 3 +- res/layout/contact_view.xml | 4 +- res/layout/group_view.xml | 5 +- res/values-cs/strings.xml | 12 + res/values-da/strings.xml | 12 + res/values-de/strings.xml | 12 + res/values-el/strings.xml | 12 + res/values-es-rUS/strings.xml | 12 + res/values-es/strings.xml | 12 + res/values-fr/strings.xml | 12 + res/values-it/strings.xml | 12 + res/values-ja/strings.xml | 12 + res/values-ko/strings.xml | 12 + res/values-nb/strings.xml | 48 +- res/values-nl/strings.xml | 12 + res/values-pl/strings.xml | 12 + res/values-pt-rPT/strings.xml | 12 + res/values-pt/strings.xml | 12 + res/values-ru/strings.xml | 12 + res/values-sv/strings.xml | 12 + res/values-tr/strings.xml | 12 + res/values-zh-rCN/strings.xml | 14 +- res/values-zh-rTW/strings.xml | 12 + res/values/strings.xml | 32 + samples/PluginDemo/Android.mk | 12 - samples/PluginDemo/AndroidManifest.xml | 41 - samples/PluginDemo/res/drawable/chat.png | Bin 615 -> 0 bytes samples/PluginDemo/res/drawable/chat_new.png | Bin 656 -> 0 bytes samples/PluginDemo/res/drawable/emo_im_angel.png | Bin 3592 -> 0 bytes samples/PluginDemo/res/drawable/emo_im_cool.png | Bin 3466 -> 0 bytes samples/PluginDemo/res/drawable/emo_im_crying.png | Bin 3558 -> 0 bytes .../PluginDemo/res/drawable/emo_im_embarrassed.png | Bin 3619 -> 0 bytes .../res/drawable/emo_im_foot_in_mouth.png | Bin 3603 -> 0 bytes samples/PluginDemo/res/drawable/emo_im_happy.png | Bin 3591 -> 0 bytes samples/PluginDemo/res/drawable/emo_im_kissing.png | Bin 3492 -> 0 bytes .../PluginDemo/res/drawable/emo_im_laughing.png | Bin 3624 -> 0 bytes .../res/drawable/emo_im_lips_are_sealed.png | Bin 3670 -> 0 bytes .../PluginDemo/res/drawable/emo_im_money_mouth.png | Bin 3649 -> 0 bytes samples/PluginDemo/res/drawable/emo_im_sad.png | Bin 3572 -> 0 bytes .../PluginDemo/res/drawable/emo_im_surprised.png | Bin 3490 -> 0 bytes .../res/drawable/emo_im_tongue_sticking_out.png | Bin 3653 -> 0 bytes .../PluginDemo/res/drawable/emo_im_undecided.png | Bin 3552 -> 0 bytes samples/PluginDemo/res/drawable/emo_im_winking.png | Bin 3568 -> 0 bytes samples/PluginDemo/res/drawable/emo_im_wtf.png | Bin 3591 -> 0 bytes samples/PluginDemo/res/drawable/emo_im_yelling.png | Bin 3575 -> 0 bytes samples/PluginDemo/res/drawable/im_logo.png | Bin 2572 -> 0 bytes .../PluginDemo/res/drawable/im_logo_splashscr.png | Bin 16280 -> 0 bytes samples/PluginDemo/res/values-cs/strings.xml | 75 - samples/PluginDemo/res/values-da/strings.xml | 75 - samples/PluginDemo/res/values-de/strings.xml | 75 - samples/PluginDemo/res/values-el/strings.xml | 75 - samples/PluginDemo/res/values-es-rUS/strings.xml | 75 - samples/PluginDemo/res/values-es/strings.xml | 75 - samples/PluginDemo/res/values-fr/strings.xml | 75 - samples/PluginDemo/res/values-it/strings.xml | 75 - samples/PluginDemo/res/values-ja/strings.xml | 75 - samples/PluginDemo/res/values-ko/strings.xml | 75 - samples/PluginDemo/res/values-nb/strings.xml | 75 - samples/PluginDemo/res/values-nl/strings.xml | 75 - samples/PluginDemo/res/values-pl/strings.xml | 75 - samples/PluginDemo/res/values-pt-rPT/strings.xml | 75 - samples/PluginDemo/res/values-pt/strings.xml | 75 - samples/PluginDemo/res/values-ru/strings.xml | 75 - samples/PluginDemo/res/values-sv/strings.xml | 75 - samples/PluginDemo/res/values-tr/strings.xml | 75 - samples/PluginDemo/res/values-zh-rCN/strings.xml | 75 - samples/PluginDemo/res/values-zh-rTW/strings.xml | 75 - samples/PluginDemo/res/values/strings.xml | 80 - .../com/android/im/plugin/demo/DemoImPlugin.java | 173 +- .../im/plugin/demo/DemoPresenceMapping.java | 4 +- src/com/android/im/app/AccountActivity.java | 20 +- src/com/android/im/app/AddContactActivity.java | 12 +- .../android/im/app/BlockedContactsActivity.java | 22 +- src/com/android/im/app/BrandingResources.java | 17 +- src/com/android/im/app/ChatBackgroundMaker.java | 8 +- src/com/android/im/app/ChatSwitcher.java | 34 +- src/com/android/im/app/ChatView.java | 75 +- src/com/android/im/app/ChooseAccountActivity.java | 5 +- src/com/android/im/app/ContactListActivity.java | 36 +- src/com/android/im/app/ContactListFilterView.java | 8 +- src/com/android/im/app/ContactListTreeAdapter.java | 46 +- src/com/android/im/app/ContactListView.java | 30 +- .../android/im/app/ContactPresenceActivity.java | 18 +- src/com/android/im/app/ContactView.java | 38 +- src/com/android/im/app/ContactsPickerActivity.java | 10 +- src/com/android/im/app/DatabaseUtils.java | 46 +- src/com/android/im/app/FrontDoorPlugin.java | 363 --- src/com/android/im/app/ImApp.java | 121 +- src/com/android/im/app/ImPluginHelper.java | 278 ++ src/com/android/im/app/ImRingtonePreference.java | 6 +- src/com/android/im/app/ImUrlActivity.java | 37 +- src/com/android/im/app/LandingPage.java | 459 +++ src/com/android/im/app/MessageView.java | 10 +- src/com/android/im/app/NewChatActivity.java | 12 +- src/com/android/im/app/PreferenceActivity.java | 14 +- src/com/android/im/app/PresenceUtils.java | 36 +- src/com/android/im/app/ProviderListItem.java | 216 ++ src/com/android/im/app/SettingActivity.java | 6 +- src/com/android/im/app/SigningInActivity.java | 24 +- src/com/android/im/app/SignoutActivity.java | 22 +- src/com/android/im/app/UserPresenceView.java | 12 +- src/com/android/im/engine/SmsService.java | 2 + src/com/android/im/imps/CirChannel.java | 8 + src/com/android/im/imps/CustomPasswordDigest.java | 22 +- src/com/android/im/imps/CustomPresenceMapping.java | 52 +- .../android/im/imps/DefaultPresenceMapping.java | 1 + src/com/android/im/imps/HttpCirChannel.java | 9 +- src/com/android/im/imps/ImpsConnectionConfig.java | 2 + .../android/im/imps/ImpsContactListManager.java | 1 + src/com/android/im/imps/ImpsPresenceUtils.java | 4 +- src/com/android/im/imps/PasswordDigest.java | 35 - src/com/android/im/imps/PresenceMapping.java | 81 - .../android/im/imps/PresencePollingManager.java | 4 +- src/com/android/im/imps/SmsCirChannel.java | 34 +- .../android/im/imps/StandardPasswordDigest.java | 1 + src/com/android/im/imps/TcpCirChannel.java | 9 +- src/com/android/im/provider/Imps.java | 2333 ++++++++++++++ src/com/android/im/provider/ImpsProvider.java | 3332 ++++++++++++++++++++ .../android/im/receiver/ImServiceAutoStarter.java | 10 +- .../im/service/AndroidHeartBeatService.java | 64 +- src/com/android/im/service/AndroidSmsService.java | 24 +- src/com/android/im/service/ChatSessionAdapter.java | 104 +- .../im/service/ContactListManagerAdapter.java | 205 +- .../android/im/service/ImConnectionAdapter.java | 58 +- src/com/android/im/service/RemoteImService.java | 116 +- src/com/android/im/service/StatusBarNotifier.java | 28 +- 221 files changed, 8370 insertions(+), 3374 deletions(-) create mode 100644 libwbxml/src/wbxml_encoder.cpp delete mode 100644 plugin/Android.mk delete mode 100644 plugin/com/android/im/plugin/IImPlugin.aidl delete mode 100644 plugin/com/android/im/plugin/IPasswordDigest.aidl delete mode 100644 plugin/com/android/im/plugin/IPresenceMapping.aidl create mode 100644 plugin/com/android/im/plugin/ImPlugin.java create mode 100644 plugin/com/android/im/plugin/PasswordDigest.java create mode 100644 plugin/com/android/im/plugin/PresenceMapping.java create mode 100644 res/color/landing_page_text.xml create mode 100644 res/color/landing_page_text_secondary.xml create mode 100644 res/drawable-hdpi/avatar_unknown.png create mode 100644 res/drawable-hdpi/background_textfield.9.png create mode 100644 res/drawable-hdpi/dashboard_highlight_selected.9.png create mode 100644 res/drawable-hdpi/dialog_im_switch_chats_strip.9.png create mode 100644 res/drawable-hdpi/droid_watermark.png create mode 100644 res/drawable-hdpi/group_chat.png create mode 100644 res/drawable-hdpi/group_chat_new.png create mode 100644 res/drawable-hdpi/ic_im_block.png create mode 100644 res/drawable-hdpi/ic_im_message_not_sent.png create mode 100644 res/drawable-hdpi/ic_launcher_im.png create mode 100644 res/drawable-hdpi/ic_menu_my_profile.png create mode 100644 res/drawable-hdpi/ic_menu_view_profile.png create mode 100644 res/drawable-hdpi/im_avatar_picture_border_normal.9.png create mode 100644 res/drawable-hdpi/im_logo.png create mode 100644 res/drawable-hdpi/imlogo_s.png create mode 100644 res/drawable-hdpi/list_item_im_bubble_default.9.png create mode 100644 res/drawable-hdpi/list_item_im_bubble_pressed.9.png create mode 100644 res/drawable-hdpi/list_item_im_bubble_selected.9.png create mode 100644 res/drawable-hdpi/picture_frame_background.9.png create mode 100644 res/drawable-hdpi/status_chat.png create mode 100644 res/drawable-hdpi/status_chat_new.png create mode 100644 res/drawable-hdpi/text_divider_horizontal.9.png create mode 100644 res/drawable-hdpi/textfield_im_pressed.9.png create mode 100644 res/drawable-hdpi/textfield_im_received.9.png create mode 100644 res/drawable-hdpi/textfield_im_selected.9.png create mode 100644 res/drawable-mdpi/avatar_unknown.png create mode 100644 res/drawable-mdpi/background_textfield.9.png create mode 100644 res/drawable-mdpi/dashboard_highlight_selected.9.png create mode 100644 res/drawable-mdpi/dialog_im_switch_chats_strip.9.png create mode 100644 res/drawable-mdpi/droid_watermark.png create mode 100644 res/drawable-mdpi/group_chat.png create mode 100644 res/drawable-mdpi/group_chat_new.png create mode 100644 res/drawable-mdpi/ic_im_block.png create mode 100644 res/drawable-mdpi/ic_im_message_not_sent.png create mode 100644 res/drawable-mdpi/ic_launcher_im.png create mode 100644 res/drawable-mdpi/ic_menu_my_profile.png create mode 100644 res/drawable-mdpi/ic_menu_view_profile.png create mode 100644 res/drawable-mdpi/im_avatar_picture_border_normal.9.png create mode 100644 res/drawable-mdpi/im_logo.png create mode 100644 res/drawable-mdpi/imlogo_s.png create mode 100644 res/drawable-mdpi/list_item_im_bubble_default.9.png create mode 100644 res/drawable-mdpi/list_item_im_bubble_pressed.9.png create mode 100644 res/drawable-mdpi/list_item_im_bubble_selected.9.png create mode 100644 res/drawable-mdpi/picture_frame_background.9.png create mode 100644 res/drawable-mdpi/status_chat.png create mode 100644 res/drawable-mdpi/status_chat_new.png create mode 100644 res/drawable-mdpi/text_divider_horizontal.9.png create mode 100644 res/drawable-mdpi/textfield_im_pressed.9.png create mode 100644 res/drawable-mdpi/textfield_im_received.9.png create mode 100644 res/drawable-mdpi/textfield_im_selected.9.png delete mode 100644 res/drawable/avatar_unknown.png delete mode 100644 res/drawable/background_textfield.9.png delete mode 100644 res/drawable/dashboard_highlight_selected.9.png create mode 100644 res/drawable/default_background.9.png delete mode 100644 res/drawable/dialog_im_switch_chats_strip.9.png delete mode 100644 res/drawable/droid_watermark.png delete mode 100644 res/drawable/group_chat.png delete mode 100644 res/drawable/group_chat_new.png delete mode 100644 res/drawable/ic_im_block.png delete mode 100644 res/drawable/ic_im_message_not_sent.png delete mode 100644 res/drawable/ic_launcher_im.png delete mode 100644 res/drawable/ic_menu_my_profile.png delete mode 100644 res/drawable/ic_menu_view_profile.png delete mode 100644 res/drawable/im_avatar_picture_border_normal.9.png delete mode 100644 res/drawable/im_logo.png delete mode 100644 res/drawable/imlogo_s.png delete mode 100644 res/drawable/list_item_im_bubble_default.9.png delete mode 100644 res/drawable/list_item_im_bubble_pressed.9.png delete mode 100644 res/drawable/list_item_im_bubble_selected.9.png delete mode 100644 res/drawable/picture_frame_background.9.png delete mode 100644 res/drawable/status_chat.png delete mode 100644 res/drawable/status_chat_new.png delete mode 100644 res/drawable/text_divider_horizontal.9.png delete mode 100644 res/drawable/textfield_im_pressed.9.png delete mode 100644 res/drawable/textfield_im_received.9.png delete mode 100644 res/drawable/textfield_im_selected.9.png create mode 100644 res/layout/account_view.xml delete mode 100644 samples/PluginDemo/Android.mk delete mode 100644 samples/PluginDemo/AndroidManifest.xml delete mode 100755 samples/PluginDemo/res/drawable/chat.png delete mode 100755 samples/PluginDemo/res/drawable/chat_new.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_angel.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_cool.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_crying.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_embarrassed.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_foot_in_mouth.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_happy.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_kissing.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_laughing.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_lips_are_sealed.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_money_mouth.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_sad.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_surprised.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_tongue_sticking_out.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_undecided.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_winking.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_wtf.png delete mode 100644 samples/PluginDemo/res/drawable/emo_im_yelling.png delete mode 100644 samples/PluginDemo/res/drawable/im_logo.png delete mode 100644 samples/PluginDemo/res/drawable/im_logo_splashscr.png delete mode 100644 samples/PluginDemo/res/values-cs/strings.xml delete mode 100644 samples/PluginDemo/res/values-da/strings.xml delete mode 100644 samples/PluginDemo/res/values-de/strings.xml delete mode 100644 samples/PluginDemo/res/values-el/strings.xml delete mode 100644 samples/PluginDemo/res/values-es-rUS/strings.xml delete mode 100644 samples/PluginDemo/res/values-es/strings.xml delete mode 100644 samples/PluginDemo/res/values-fr/strings.xml delete mode 100644 samples/PluginDemo/res/values-it/strings.xml delete mode 100644 samples/PluginDemo/res/values-ja/strings.xml delete mode 100644 samples/PluginDemo/res/values-ko/strings.xml delete mode 100644 samples/PluginDemo/res/values-nb/strings.xml delete mode 100644 samples/PluginDemo/res/values-nl/strings.xml delete mode 100644 samples/PluginDemo/res/values-pl/strings.xml delete mode 100644 samples/PluginDemo/res/values-pt-rPT/strings.xml delete mode 100644 samples/PluginDemo/res/values-pt/strings.xml delete mode 100644 samples/PluginDemo/res/values-ru/strings.xml delete mode 100644 samples/PluginDemo/res/values-sv/strings.xml delete mode 100644 samples/PluginDemo/res/values-tr/strings.xml delete mode 100644 samples/PluginDemo/res/values-zh-rCN/strings.xml delete mode 100644 samples/PluginDemo/res/values-zh-rTW/strings.xml delete mode 100644 samples/PluginDemo/res/values/strings.xml delete mode 100644 src/com/android/im/app/FrontDoorPlugin.java create mode 100644 src/com/android/im/app/ImPluginHelper.java create mode 100644 src/com/android/im/app/LandingPage.java create mode 100644 src/com/android/im/app/ProviderListItem.java delete mode 100644 src/com/android/im/imps/PasswordDigest.java delete mode 100644 src/com/android/im/imps/PresenceMapping.java create mode 100644 src/com/android/im/provider/Imps.java create mode 100644 src/com/android/im/provider/ImpsProvider.java diff --git a/Android.mk b/Android.mk index a3a1856..4eecd43 100644 --- a/Android.mk +++ b/Android.mk @@ -16,22 +16,13 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) \ src/com/android/im/ISubscriptionListener.aidl \ src/com/android/im/IConnectionCreationListener.aidl \ -# Filter out the plugin and samples when build IM.apk -LOCAL_SRC_FILES := $(filter-out \ - plugin/% samples/% \ - ,$(LOCAL_SRC_FILES)) - LOCAL_PACKAGE_NAME := IM -# TODO: Remove dependency of application on the test runner (android.test.runner) -# library. -LOCAL_JAVA_LIBRARIES := android.test.runner \ - com.android.im.plugin \ -# com.android.providers.im.plugin - -# LOCAL_REQUIRED_MODULES must go before BUILD_PACKAGE -LOCAL_REQUIRED_MODULES := libwbxml libwbxml_jni ImProvider +LOCAL_JNI_SHARED_LIBRARIES := libwbxml_jni -include $(BUILD_PACKAGE) +#Disable building the APK; we are checking in the pre-built version which +#contains the credential plug-in instead. Note the libwbxml_jni has to be +#enabled because so won't be extracted from the system APK +#include $(BUILD_PACKAGE) include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index c0b26aa..cec0a6b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -22,8 +22,8 @@ package="com.android.im" android:sharedUserId="android.uid.im" android:sharedUserLabel="@string/perm_label"> - - + + @@ -40,20 +40,22 @@ android:label="@string/perm_label" android:description="@string/perm_desc" /> + + + + - - - - - - - - - + + @@ -93,22 +103,39 @@ - + + + + + + + + + + + + + + + - + - + - + @@ -127,7 +154,7 @@ - + @@ -141,17 +168,17 @@ - + - + - + @@ -159,7 +186,7 @@ - + @@ -167,7 +194,7 @@ - + @@ -175,7 +202,7 @@ - + @@ -190,7 +217,7 @@ - + @@ -207,6 +234,22 @@ + + + + diff --git a/libwbxml/Android.mk b/libwbxml/Android.mk index 4836f0d..c9450c1 100644 --- a/libwbxml/Android.mk +++ b/libwbxml/Android.mk @@ -9,6 +9,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ src/wbxml_parser.cpp \ + src/wbxml_encoder.cpp \ src/imps_encoder.cpp \ src/csp13tags_hash.c \ src/csp13values_hash.c \ @@ -29,7 +30,7 @@ LOCAL_MODULE_TAGS := $(wbxml_module_tags) LOCAL_MODULE := libwbxml -include $(BUILD_SHARED_LIBRARY) +include $(BUILD_STATIC_LIBRARY) # xml2wbxml library: libxml2wbxml.so # --------------------------------------- @@ -49,9 +50,10 @@ LOCAL_CFLAGS += -DPLATFORM_ANDROID LOCAL_SHARED_LIBRARIES += \ libutils \ - libwbxml \ libexpat +LOCAL_STATIC_LIBRARIES := libwbxml + LOCAL_MODULE_TAGS := $(wbxml_module_tags) LOCAL_MODULE := libxml2wbxml @@ -79,13 +81,14 @@ LOCAL_CFLAGS += -DSUPPORT_SYNCML LOCAL_SRC_FILES += test/syncml_parser_test.cpp LOCAL_SHARED_LIBRARIES += \ - libwbxml \ libxml2wbxml \ libembunit \ libutils \ libexpat -LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_LIBRARIES := libwbxml + +LOCAL_MODULE_TAGS := tests LOCAL_MODULE := wbxmltest @@ -100,16 +103,15 @@ LOCAL_SRC_FILES := \ LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/include \ - external/expat/lib \ $(JNI_H_INCLUDE) LOCAL_CFLAGS += -DPLATFORM_ANDROID LOCAL_SHARED_LIBRARIES += \ - libwbxml \ libutils \ libcutils \ - libexpat + +LOCAL_STATIC_LIBRARIES := libwbxml LOCAL_MODULE_TAGS := $(wbxml_module_tags) diff --git a/libwbxml/include/imps_encoder.h b/libwbxml/include/imps_encoder.h index c7300b0..fda9651 100644 --- a/libwbxml/include/imps_encoder.h +++ b/libwbxml/include/imps_encoder.h @@ -25,7 +25,7 @@ class ImpsWbxmlEncoder : public WbxmlEncoder { public: ImpsWbxmlEncoder(int publicid) : - mPublicId(publicid) + WbxmlEncoder(publicid) { reset(); } @@ -53,28 +53,12 @@ public: EncoderError endElement(); private: - int mPublicId; int mTagCodePage; - string mResult; string mCurrElement; int mDepth; - EncoderError encodeInteger(const char *chars, int len); - EncoderError encodeDatetime(const char *chars, int len); EncoderError encodeString(const char *chars, int len); EncoderError encodeAttrib(const char *name, const char *value); - void encodeInlinedStr(const char *s, int len); - void encodeMbuint(uint32_t i); - - void appendResult(int ch) - { - mResult += (char)ch; - } - - void appendResult(const char *s, int len) - { - mResult.append(s, len); - } }; #endif diff --git a/libwbxml/include/wbxml_encoder.h b/libwbxml/include/wbxml_encoder.h index a1b653a..bbd21c8 100644 --- a/libwbxml/include/wbxml_encoder.h +++ b/libwbxml/include/wbxml_encoder.h @@ -20,6 +20,8 @@ #include #include "wbxml_const.h" +#include "wbxml_stl.h" + class WbxmlHandler { @@ -43,6 +45,8 @@ enum EncoderError { class WbxmlEncoder { public: + WbxmlEncoder(int publicId):mPublicId(publicId) {} + virtual ~WbxmlEncoder() {} void setWbxmlHandler(WbxmlHandler * handler) @@ -60,8 +64,44 @@ public: */ virtual void reset() = 0; + static bool isXmlWhitespace(int ch); + static bool parseUint(const char * s, int len, uint32_t *res); + protected: WbxmlHandler * mHandler; + int mPublicId; + + EncoderError encodeInteger(const char *chars, int len); + EncoderError encodeDatetime(const char *chars, int len); + void encodeInlinedStr(const char *s, int len); + void encodeMbuint(uint32_t i); + + void clearResult() + { + mResult.clear(); + } + + void appendResult(int ch) + { + mResult += (char)ch; + } + + void appendResult(const char *s, int len) + { + mResult.append(s, len); + } + + void sendResult(); + + /** + * Append a string into the string table, return the index of the string in + * the string table. + */ + int appendToStringTable(const char *s); + +private: + string mResult; + vector mStringTable; }; #endif diff --git a/libwbxml/src/imps_encoder.cpp b/libwbxml/src/imps_encoder.cpp index 959486c..26a30ee 100644 --- a/libwbxml/src/imps_encoder.cpp +++ b/libwbxml/src/imps_encoder.cpp @@ -42,34 +42,14 @@ static const XmlnsPrefix csp13xmlns[] = { { "http://www.openmobilealliance.org/DTD/IMPS-TRC", 0x0d }, }; -static bool isXmlWhitespace(int ch) -{ - return ch == ' ' || ch == 9 || ch == 0xd || ch == 0xa; -} - static bool isDatetimeElement(const char *name) { return (strcmp("DateTime", name) == 0 || strcmp("DeliveryTime", name) == 0); } -static bool parseUint(const char * s, int len, uint32_t *res) -{ - string str(s, len); - char *end; - long long val = strtoll(str.c_str(), &end, 10); - if (*end != 0 || val < 0 || val > 0xFFFFFFFFU) { - return false; - } - *res = (uint32_t)val; - return true; -} - void ImpsWbxmlEncoder::reset() { - // WBXML 1.3, UTF-8, no string table - char header[4] = {0x03, (char)mPublicId, 0x6A, 0x00}; - mResult.clear(); - mResult.append(header, sizeof(header)); + clearResult(); mTagCodePage = 0; mCurrElement.clear(); @@ -82,9 +62,11 @@ EncoderError ImpsWbxmlEncoder::startElement(const char *name, const char **atts) return ERROR_INVALID_DATA; } + bool isUnknownTag = false; int stag = csp13TagNameToKey(name); if (stag == -1) { - return ERROR_UNSUPPORTED_TAG; + stag = TOKEN_LITERAL; + isUnknownTag = true; } mDepth++; mCurrElement = name; @@ -102,6 +84,11 @@ EncoderError ImpsWbxmlEncoder::startElement(const char *name, const char **atts) stag |= 0x80; // has attribute } appendResult(stag); + + if (isUnknownTag) { + int index = appendToStringTable(name); + encodeMbuint(index); + } if (stag & 0x80) { for (size_t i = 0; atts[i]; i += 2) { EncoderError err = encodeAttrib(atts[i], atts[i + 1]); @@ -164,69 +151,9 @@ EncoderError ImpsWbxmlEncoder::endElement() } appendResult(TOKEN_END); mCurrElement.clear(); - if (mDepth == 0 && mHandler) { - mHandler->wbxmlData(mResult.c_str(), mResult.size()); - } - return NO_ERROR; -} - -EncoderError ImpsWbxmlEncoder::encodeInteger(const char *chars, int len) -{ - uint32_t val; - if (!parseUint(chars, len, &val)) { - return ERROR_INVALID_INTEGER_VALUE; - } - - appendResult(TOKEN_OPAQUE); - uint32_t mask = 0xff000000U; - int numBytes = 4; - while (!(val & mask) && mask) { - numBytes--; - mask >>= 8; - } - if (!numBytes) { - // Zero value. We generate at least 1 byte OPAQUE data. - // libwbxml2 generates 0 byte long OPAQUE data (0xC3 0x00) in this case. - numBytes = 1; - } - - appendResult(numBytes); - while (numBytes) { - numBytes--; - appendResult((val >> (numBytes * 8)) & 0xff); - } - - return NO_ERROR; -} - -EncoderError ImpsWbxmlEncoder::encodeDatetime(const char *chars, int len) -{ - // to make life easier we accept only yyyymmddThhmmssZ - if (len != 16 || chars[8] != 'T' || chars[15] != 'Z') { - return ERROR_INVALID_DATETIME_VALUE; + if (mDepth == 0) { + sendResult(); } - appendResult(TOKEN_OPAQUE); - appendResult(6); - - uint32_t year, month, day, hour, min, sec; - if (!parseUint(chars, 4, &year) - || !parseUint(chars + 4, 2, &month) - || !parseUint(chars + 6, 2, &day) - || !parseUint(chars + 9, 2, &hour) - || !parseUint(chars + 11,2, &min) - || !parseUint(chars + 13,2, &sec)) { - return ERROR_INVALID_DATETIME_VALUE; - } - if (year > 4095 || month > 12 || day > 31 || hour > 23 || min > 59 || sec > 59) { - return ERROR_INVALID_DATETIME_VALUE; - } - - appendResult(year >> 6); - appendResult(((year & 0x3f) << 2) | (month >> 2)); - appendResult(((month & 0x3) << 6) | (day << 1) | (hour >> 4)); - appendResult(((hour & 0xf) << 4) | (min >> 2)); - appendResult(((min & 0x2) << 6) | sec); - appendResult('Z'); return NO_ERROR; } @@ -251,7 +178,9 @@ EncoderError ImpsWbxmlEncoder::encodeAttrib(const char *name, const char *value) return ERROR_UNSUPPORTED_ATTR; } int valueLen = strlen(value); - for (size_t i = 0; i < sizeof(csp13xmlns) / sizeof(csp13xmlns[0]); i++) { + size_t csp13xmlnsCount = sizeof(csp13xmlns) / sizeof(csp13xmlns[0]); + size_t i; + for (i = 0; i < csp13xmlnsCount; i++) { const char * prefix = csp13xmlns[i].prefix; int prefixLen = strlen(csp13xmlns[i].prefix); if (strncmp(prefix, value, prefixLen) == 0) { @@ -262,31 +191,12 @@ EncoderError ImpsWbxmlEncoder::encodeAttrib(const char *name, const char *value) return NO_ERROR; } } + if (i == csp13xmlnsCount) { + // not predefined attribute + appendResult(TOKEN_LITERAL); + int index = appendToStringTable(name); + encodeMbuint(index); + } encodeInlinedStr(value, valueLen); return NO_ERROR; } - -void ImpsWbxmlEncoder::encodeInlinedStr(const char *s, int len) -{ - // TODO: move this to WbxmlEncoder - // TODO: handle ENTITY - appendResult(TOKEN_STR_I); - appendResult(s, len); - appendResult('\0'); -} - -void ImpsWbxmlEncoder::encodeMbuint(uint32_t val) -{ - char buf[32 / 7 + 1]; // each byte holds up to 7 bits - int i = sizeof(buf); - - buf[--i] = val & 0x7f; - val >>= 7; - while ((i > 0) && (val & 0x7f)) { - buf[--i] = 0x80 | (val & 0x7f); - val >>= 7; - } - - appendResult(buf + i, sizeof(buf) - i); -} - diff --git a/libwbxml/src/wbxml_encoder.cpp b/libwbxml/src/wbxml_encoder.cpp new file mode 100644 index 0000000..c84b90e --- /dev/null +++ b/libwbxml/src/wbxml_encoder.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2009 Esmertec AG. + * 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. + */ + +#include +#include +#include "imps_encoder.h" + +bool WbxmlEncoder::isXmlWhitespace(int ch) +{ + return ch == ' ' || ch == 9 || ch == 0xd || ch == 0xa; +} + +bool WbxmlEncoder::parseUint(const char * s, int len, uint32_t *res) +{ + string str(s, len); + char *end; + long long val = strtoll(str.c_str(), &end, 10); + if (*end != 0 || val < 0 || val > 0xFFFFFFFFU) { + return false; + } + *res = (uint32_t)val; + return true; +} + +EncoderError WbxmlEncoder::encodeInteger(const char *chars, int len) +{ + uint32_t val; + if (!parseUint(chars, len, &val)) { + return ERROR_INVALID_INTEGER_VALUE; + } + + appendResult(TOKEN_OPAQUE); + uint32_t mask = 0xff000000U; + int numBytes = 4; + while (!(val & mask) && mask) { + numBytes--; + mask >>= 8; + } + if (!numBytes) { + // Zero value. We generate at least 1 byte OPAQUE data. + // libwbxml2 generates 0 byte long OPAQUE data (0xC3 0x00) in this case. + numBytes = 1; + } + + appendResult(numBytes); + while (numBytes) { + numBytes--; + appendResult((val >> (numBytes * 8)) & 0xff); + } + + return NO_ERROR; +} + +EncoderError WbxmlEncoder::encodeDatetime(const char *chars, int len) +{ + // to make life easier we accept only yyyymmddThhmmssZ + if (len != 16 || chars[8] != 'T' || chars[15] != 'Z') { + return ERROR_INVALID_DATETIME_VALUE; + } + appendResult(TOKEN_OPAQUE); + appendResult(6); + + uint32_t year, month, day, hour, min, sec; + if (!parseUint(chars, 4, &year) + || !parseUint(chars + 4, 2, &month) + || !parseUint(chars + 6, 2, &day) + || !parseUint(chars + 9, 2, &hour) + || !parseUint(chars + 11,2, &min) + || !parseUint(chars + 13,2, &sec)) { + return ERROR_INVALID_DATETIME_VALUE; + } + if (year > 4095 || month > 12 || day > 31 || hour > 23 || min > 59 || sec > 59) { + return ERROR_INVALID_DATETIME_VALUE; + } + + appendResult(year >> 6); + appendResult(((year & 0x3f) << 2) | (month >> 2)); + appendResult(((month & 0x3) << 6) | (day << 1) | (hour >> 4)); + appendResult(((hour & 0xf) << 4) | (min >> 2)); + appendResult(((min & 0x2) << 6) | sec); + appendResult('Z'); + return NO_ERROR; +} + +void WbxmlEncoder::encodeInlinedStr(const char *s, int len) +{ + // TODO: handle ENTITY + appendResult(TOKEN_STR_I); + appendResult(s, len); + appendResult('\0'); +} + +void WbxmlEncoder::encodeMbuint(uint32_t val) +{ + char buf[32 / 7 + 1]; // each byte holds up to 7 bits + int i = sizeof(buf); + + buf[--i] = val & 0x7f; + val >>= 7; + while ((i > 0) && (val & 0x7f)) { + buf[--i] = 0x80 | (val & 0x7f); + val >>= 7; + } + + appendResult(buf + i, sizeof(buf) - i); +} + +int WbxmlEncoder::appendToStringTable(const char *s) +{ + int stringTableSize = mStringTable.size(); + int offset = 0; + + // search the string table to find if the string already exist + int index = 0; + for (; index < stringTableSize; index++) { + if (mStringTable[index] == s) { + break; + } + offset += mStringTable[index].length(); + ++offset; // '\0' for each string in the table + } + if (index == stringTableSize) { + // not found, insert a new one + mStringTable.push_back(s); + } + return offset; +} + +void WbxmlEncoder::sendResult() +{ + if (mHandler) { + string data; + string tmp = mResult; + mResult = data; + + // WBXML 1.3, UTF-8 + char header[3] = { 0x03, (char) mPublicId, 0x6A }; + appendResult(header, 3); + + // calculate the length of string table + int len = 0; + for (int i = 0; i < mStringTable.size(); i++) { + len += mStringTable[i].length(); + ++len; + } + + encodeMbuint(len); + + // encode each string in the table + for (int i = 0; i < mStringTable.size(); i++) { + mResult += mStringTable[i]; + mResult += '\0'; + } + + mResult += tmp; + + mHandler->wbxmlData(mResult.c_str(), mResult.size()); + } +} diff --git a/plugin/Android.mk b/plugin/Android.mk deleted file mode 100644 index 28ccd21..0000000 --- a/plugin/Android.mk +++ /dev/null @@ -1,31 +0,0 @@ -# -# 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. -# -LOCAL_PATH := $(call my-dir) - -# the library -# ============================================================ -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - $(call all-subdir-java-files) \ - com/android/im/plugin/IImPlugin.aidl \ - com/android/im/plugin/IPasswordDigest.aidl \ - com/android/im/plugin/IPresenceMapping.aidl \ - -LOCAL_MODULE:= com.android.im.plugin - -include $(BUILD_JAVA_LIBRARY) diff --git a/plugin/com/android/im/plugin/BrandingResourceIDs.java b/plugin/com/android/im/plugin/BrandingResourceIDs.java index b53c143..70b48c6 100644 --- a/plugin/com/android/im/plugin/BrandingResourceIDs.java +++ b/plugin/com/android/im/plugin/BrandingResourceIDs.java @@ -20,7 +20,37 @@ package com.android.im.plugin; * Defines the IDs of branding resources. * */ -public interface BrandingResourceIDs extends android.im.BrandingResourceIDs { +public interface BrandingResourceIDs { + + /** + * The logo icon of the provider which is displayed in the landing page. + */ + public static final int DRAWABLE_LOGO = 100; + /** + * The icon of online presence status. + */ + public static final int DRAWABLE_PRESENCE_ONLINE = 102; + /** + * The icon of busy presence status. + */ + public static final int DRAWABLE_PRESENCE_BUSY = 103; + /** + * The icon of away presence status. + */ + public static final int DRAWABLE_PRESENCE_AWAY = 104; + /** + * The icon of invisible presence status. + */ + public static final int DRAWABLE_PRESENCE_INVISIBLE = 105; + /** + * The icon of offline presence status. + */ + public static final int DRAWABLE_PRESENCE_OFFLINE = 106; + /** + * The label of the menu to go to the contact list screen. + */ + public static final int STRING_MENU_CONTACT_LIST = 107; + /** * The image displayed on the splash screen while logging in. */ diff --git a/plugin/com/android/im/plugin/IImPlugin.aidl b/plugin/com/android/im/plugin/IImPlugin.aidl deleted file mode 100644 index b641e86..0000000 --- a/plugin/com/android/im/plugin/IImPlugin.aidl +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.im.plugin; - -interface IImPlugin { - /** - * Gets a map of branding resources for the provider. The keys are defined - * in {@link com.android.im.plugin.BrandingResourceIDs}. The values are the - * resource identifiers generated by the aapt tool. - * - * @return The map of branding resources. - */ - Map getResourceMap(); - - /** - * Gets an array of IDs of the smiley icons. The sequence of the IDs must - * much the strings defined in {@link BrandingResourceIDs#STRING_ARRAY_SMILEY_TEXTS}. - * - * The IDs are generated by the aapt tool. - * - * @return An array of the IDs of the smiley icons. - */ - int[] getSmileyIconIds(); - - /** - * Gets the configuration for the provider. The keys MUST match the values - * defined in {@link ImConfigNames} and {@link ImpsConfigNames} - * - * @return the configuration for the provider. - */ - Map getProviderConfig(); -} diff --git a/plugin/com/android/im/plugin/IPasswordDigest.aidl b/plugin/com/android/im/plugin/IPasswordDigest.aidl deleted file mode 100644 index f6bed17..0000000 --- a/plugin/com/android/im/plugin/IPasswordDigest.aidl +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.im.plugin; - -/** - * The password digest method used in IMPS login transaction. - */ -interface IPasswordDigest { - /** - * Gets an array of supported digest schema. - * - * @return an array of digest schema - */ - String[] getSupportedDigestSchema(); - - /** - * Generates the digest bytes of the password. - * - * @param schema The digest schema to use. - * @param nonce The nonce string returned by the server. - * @param password The user password. - * @return The digest bytes of the password. - */ - String digest(String schema, String nonce, String password); -} diff --git a/plugin/com/android/im/plugin/IPresenceMapping.aidl b/plugin/com/android/im/plugin/IPresenceMapping.aidl deleted file mode 100644 index 3b6ffc4..0000000 --- a/plugin/com/android/im/plugin/IPresenceMapping.aidl +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.im.plugin; - -/** - * The methods used to map presence value sent in protocol to predefined - * presence status. - */ -interface IPresenceMapping { - /** - * Tells if the mapping needs all presence values sent in protocol. If this - * method returns true, the framework will pass all the presence values - * received from the server when map to the predefined status. - * - * @return true if needs; false otherwise. - */ - boolean requireAllPresenceValues(); - - /** - * Map the presence values sent in protocol to the predefined presence - * status. - * - * @param onlineStatus The value of presence <OnlineStatus> received - * from the server. - * @param userAvailability The value of presence <UserAvailibility> - * received from the server. - * @param allValues The whole presence values received from the server. - * @return a predefined status. - * @see #requireAllPresenceValues() - */ - int getPresenceStatus(boolean onlineStatus, String userAvailability, - in Map allValues); - - /** - * Gets the value of <OnlineStatus> will be sent to the server when - * update presence to the predefined status. - * - * @param status the predefined status. - * @return The value of <OnlineStatus> will be sent to the server - */ - boolean getOnlineStatus(int status); - - /** - * Gets the value of <UserAvaibility> will be sent to the server when - * update presence to the predefined status. - * - * @param status the predefined status. - * @return The value of <UserAvaibility> will be sent to the server - */ - String getUserAvaibility(int status); - - /** - * Gets the extra presence values other than <OnlineStatus> and - * <UserAvaibility> will be sent to the server when update presence to - * the predefined status. - * - * @param status the predefined status. - * @return The extra values that will be sent to the server. - */ - Map getExtra(int status); - - /** - * Gets an array of the supported presence status. The client can only update - * presence to the values in the array. - * - * @return an array of the supported presence status. - */ - int[] getSupportedPresenceStatus(); -} diff --git a/plugin/com/android/im/plugin/ImPlugin.java b/plugin/com/android/im/plugin/ImPlugin.java new file mode 100644 index 0000000..27ad319 --- /dev/null +++ b/plugin/com/android/im/plugin/ImPlugin.java @@ -0,0 +1,48 @@ +/* + * 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.im.plugin; + +import java.util.Map; + +public interface ImPlugin { + /** + * Gets a map of branding resources for the provider. The keys are defined + * in {@link com.android.im.plugin.BrandingResourceIDs}. The values are the + * resource identifiers generated by the aapt tool. + * + * @return The map of branding resources. + */ + Map getResourceMap(); + + /** + * Gets an array of IDs of the smiley icons. The sequence of the IDs must + * much the strings defined in {@link BrandingResourceIDs#STRING_ARRAY_SMILEY_TEXTS}. + * + * The IDs are generated by the aapt tool. + * + * @return An array of the IDs of the smiley icons. + */ + int[] getSmileyIconIds(); + + /** + * Gets the configuration for the provider. The keys MUST match the values + * defined in {@link ImConfigNames} and {@link ImpsConfigNames} + * + * @return the configuration for the provider. + */ + Map getProviderConfig(); +} diff --git a/plugin/com/android/im/plugin/ImPluginConstants.java b/plugin/com/android/im/plugin/ImPluginConstants.java index a512744..d51e5ec 100644 --- a/plugin/com/android/im/plugin/ImPluginConstants.java +++ b/plugin/com/android/im/plugin/ImPluginConstants.java @@ -25,7 +25,7 @@ public class ImPluginConstants { /** * The name of the provider. It should match the values defined in - * {@link android.provider.Im.ProviderNames}. + * {@link com.android.im.provider.Imps.ProviderNames}. */ public static final String METADATA_PROVIDER_NAME = "com.android.im.provider_name"; diff --git a/plugin/com/android/im/plugin/PasswordDigest.java b/plugin/com/android/im/plugin/PasswordDigest.java new file mode 100644 index 0000000..1a03243 --- /dev/null +++ b/plugin/com/android/im/plugin/PasswordDigest.java @@ -0,0 +1,43 @@ +/* + * 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.im.plugin; + +import com.android.im.engine.ImException; + +/** + * The password digest method used in IMPS login transaction. + */ +public interface PasswordDigest { + /** + * Gets an array of supported digest schema. + * + * @return an array of digest schema + */ + String[] getSupportedDigestSchema(); + + /** + * Generates the digest bytes of the password. + * + * @param schema The digest schema to use. + * @param nonce The nonce string returned by the server. + * @param password The user password. + * @return The digest bytes of the password. + * @throws ImException + */ + String digest(String schema, String nonce, String password) throws ImException; +} diff --git a/plugin/com/android/im/plugin/PresenceMapping.java b/plugin/com/android/im/plugin/PresenceMapping.java new file mode 100644 index 0000000..74e0fd6 --- /dev/null +++ b/plugin/com/android/im/plugin/PresenceMapping.java @@ -0,0 +1,85 @@ +/* + * 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.im.plugin; + +import java.util.Map; + +/** + * The methods used to map presence value sent in protocol to predefined + * presence status. + */ +public interface PresenceMapping { + /** + * Tells if the mapping needs all presence values sent in protocol. If this + * method returns true, the framework will pass all the presence values + * received from the server when map to the predefined status. + * + * @return true if needs; false otherwise. + */ + boolean requireAllPresenceValues(); + + /** + * Map the presence values sent in protocol to the predefined presence + * status. + * + * @param onlineStatus The value of presence <OnlineStatus> received + * from the server. + * @param userAvailability The value of presence <UserAvailibility> + * received from the server. + * @param allValues The whole presence values received from the server. + * @return a predefined status. + * @see #requireAllPresenceValues() + */ + int getPresenceStatus(boolean onlineStatus, String userAvailability, + Map allValues); + + /** + * Gets the value of <OnlineStatus> will be sent to the server when + * update presence to the predefined status. + * + * @param status the predefined status. + * @return The value of <OnlineStatus> will be sent to the server + */ + boolean getOnlineStatus(int status); + + /** + * Gets the value of <UserAvaibility> will be sent to the server when + * update presence to the predefined status. + * + * @param status the predefined status. + * @return The value of <UserAvaibility> will be sent to the server + */ + String getUserAvaibility(int status); + + /** + * Gets the extra presence values other than <OnlineStatus> and + * <UserAvaibility> will be sent to the server when update presence to + * the predefined status. + * + * @param status the predefined status. + * @return The extra values that will be sent to the server. + */ + Map getExtra(int status); + + /** + * Gets an array of the supported presence status. The client can only update + * presence to the values in the array. + * + * @return an array of the supported presence status. + */ + int[] getSupportedPresenceStatus(); +} diff --git a/res/color/landing_page_text.xml b/res/color/landing_page_text.xml new file mode 100644 index 0000000..6e967d9 --- /dev/null +++ b/res/color/landing_page_text.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/res/color/landing_page_text_secondary.xml b/res/color/landing_page_text_secondary.xml new file mode 100644 index 0000000..78cc390 --- /dev/null +++ b/res/color/landing_page_text_secondary.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/res/drawable-hdpi/avatar_unknown.png b/res/drawable-hdpi/avatar_unknown.png new file mode 100644 index 0000000..7c34f5c Binary files /dev/null and b/res/drawable-hdpi/avatar_unknown.png differ diff --git a/res/drawable-hdpi/background_textfield.9.png b/res/drawable-hdpi/background_textfield.9.png new file mode 100644 index 0000000..6c101ac Binary files /dev/null and b/res/drawable-hdpi/background_textfield.9.png differ diff --git a/res/drawable-hdpi/dashboard_highlight_selected.9.png b/res/drawable-hdpi/dashboard_highlight_selected.9.png new file mode 100644 index 0000000..483fe3b Binary files /dev/null and b/res/drawable-hdpi/dashboard_highlight_selected.9.png differ diff --git a/res/drawable-hdpi/dialog_im_switch_chats_strip.9.png b/res/drawable-hdpi/dialog_im_switch_chats_strip.9.png new file mode 100644 index 0000000..262ab2d Binary files /dev/null and b/res/drawable-hdpi/dialog_im_switch_chats_strip.9.png differ diff --git a/res/drawable-hdpi/droid_watermark.png b/res/drawable-hdpi/droid_watermark.png new file mode 100644 index 0000000..7aeb550 Binary files /dev/null and b/res/drawable-hdpi/droid_watermark.png differ diff --git a/res/drawable-hdpi/group_chat.png b/res/drawable-hdpi/group_chat.png new file mode 100644 index 0000000..09b8944 Binary files /dev/null and b/res/drawable-hdpi/group_chat.png differ diff --git a/res/drawable-hdpi/group_chat_new.png b/res/drawable-hdpi/group_chat_new.png new file mode 100644 index 0000000..49807bf Binary files /dev/null and b/res/drawable-hdpi/group_chat_new.png differ diff --git a/res/drawable-hdpi/ic_im_block.png b/res/drawable-hdpi/ic_im_block.png new file mode 100644 index 0000000..1854e82 Binary files /dev/null and b/res/drawable-hdpi/ic_im_block.png differ diff --git a/res/drawable-hdpi/ic_im_message_not_sent.png b/res/drawable-hdpi/ic_im_message_not_sent.png new file mode 100644 index 0000000..aba1b56 Binary files /dev/null and b/res/drawable-hdpi/ic_im_message_not_sent.png differ diff --git a/res/drawable-hdpi/ic_launcher_im.png b/res/drawable-hdpi/ic_launcher_im.png new file mode 100644 index 0000000..3ea3b70 Binary files /dev/null and b/res/drawable-hdpi/ic_launcher_im.png differ diff --git a/res/drawable-hdpi/ic_menu_my_profile.png b/res/drawable-hdpi/ic_menu_my_profile.png new file mode 100644 index 0000000..ed9a563 Binary files /dev/null and b/res/drawable-hdpi/ic_menu_my_profile.png differ diff --git a/res/drawable-hdpi/ic_menu_view_profile.png b/res/drawable-hdpi/ic_menu_view_profile.png new file mode 100644 index 0000000..96e20c4 Binary files /dev/null and b/res/drawable-hdpi/ic_menu_view_profile.png differ diff --git a/res/drawable-hdpi/im_avatar_picture_border_normal.9.png b/res/drawable-hdpi/im_avatar_picture_border_normal.9.png new file mode 100644 index 0000000..2367f98 Binary files /dev/null and b/res/drawable-hdpi/im_avatar_picture_border_normal.9.png differ diff --git a/res/drawable-hdpi/im_logo.png b/res/drawable-hdpi/im_logo.png new file mode 100644 index 0000000..8681c8c Binary files /dev/null and b/res/drawable-hdpi/im_logo.png differ diff --git a/res/drawable-hdpi/imlogo_s.png b/res/drawable-hdpi/imlogo_s.png new file mode 100644 index 0000000..064cdbf Binary files /dev/null and b/res/drawable-hdpi/imlogo_s.png differ diff --git a/res/drawable-hdpi/list_item_im_bubble_default.9.png b/res/drawable-hdpi/list_item_im_bubble_default.9.png new file mode 100644 index 0000000..048409d Binary files /dev/null and b/res/drawable-hdpi/list_item_im_bubble_default.9.png differ diff --git a/res/drawable-hdpi/list_item_im_bubble_pressed.9.png b/res/drawable-hdpi/list_item_im_bubble_pressed.9.png new file mode 100644 index 0000000..29b3bfc Binary files /dev/null and b/res/drawable-hdpi/list_item_im_bubble_pressed.9.png differ diff --git a/res/drawable-hdpi/list_item_im_bubble_selected.9.png b/res/drawable-hdpi/list_item_im_bubble_selected.9.png new file mode 100644 index 0000000..fbd0914 Binary files /dev/null and b/res/drawable-hdpi/list_item_im_bubble_selected.9.png differ diff --git a/res/drawable-hdpi/picture_frame_background.9.png b/res/drawable-hdpi/picture_frame_background.9.png new file mode 100644 index 0000000..45858df Binary files /dev/null and b/res/drawable-hdpi/picture_frame_background.9.png differ diff --git a/res/drawable-hdpi/status_chat.png b/res/drawable-hdpi/status_chat.png new file mode 100644 index 0000000..a1d7f53 Binary files /dev/null and b/res/drawable-hdpi/status_chat.png differ diff --git a/res/drawable-hdpi/status_chat_new.png b/res/drawable-hdpi/status_chat_new.png new file mode 100644 index 0000000..f8e829a Binary files /dev/null and b/res/drawable-hdpi/status_chat_new.png differ diff --git a/res/drawable-hdpi/text_divider_horizontal.9.png b/res/drawable-hdpi/text_divider_horizontal.9.png new file mode 100644 index 0000000..3ea9e90 Binary files /dev/null and b/res/drawable-hdpi/text_divider_horizontal.9.png differ diff --git a/res/drawable-hdpi/textfield_im_pressed.9.png b/res/drawable-hdpi/textfield_im_pressed.9.png new file mode 100644 index 0000000..660b138 Binary files /dev/null and b/res/drawable-hdpi/textfield_im_pressed.9.png differ diff --git a/res/drawable-hdpi/textfield_im_received.9.png b/res/drawable-hdpi/textfield_im_received.9.png new file mode 100644 index 0000000..4ee6baa Binary files /dev/null and b/res/drawable-hdpi/textfield_im_received.9.png differ diff --git a/res/drawable-hdpi/textfield_im_selected.9.png b/res/drawable-hdpi/textfield_im_selected.9.png new file mode 100644 index 0000000..efbc26a Binary files /dev/null and b/res/drawable-hdpi/textfield_im_selected.9.png differ diff --git a/res/drawable-mdpi/avatar_unknown.png b/res/drawable-mdpi/avatar_unknown.png new file mode 100644 index 0000000..3a338e8 Binary files /dev/null and b/res/drawable-mdpi/avatar_unknown.png differ diff --git a/res/drawable-mdpi/background_textfield.9.png b/res/drawable-mdpi/background_textfield.9.png new file mode 100644 index 0000000..679e00f Binary files /dev/null and b/res/drawable-mdpi/background_textfield.9.png differ diff --git a/res/drawable-mdpi/dashboard_highlight_selected.9.png b/res/drawable-mdpi/dashboard_highlight_selected.9.png new file mode 100644 index 0000000..473e059 Binary files /dev/null and b/res/drawable-mdpi/dashboard_highlight_selected.9.png differ diff --git a/res/drawable-mdpi/dialog_im_switch_chats_strip.9.png b/res/drawable-mdpi/dialog_im_switch_chats_strip.9.png new file mode 100644 index 0000000..1fe2c11 Binary files /dev/null and b/res/drawable-mdpi/dialog_im_switch_chats_strip.9.png differ diff --git a/res/drawable-mdpi/droid_watermark.png b/res/drawable-mdpi/droid_watermark.png new file mode 100644 index 0000000..785f7b9 Binary files /dev/null and b/res/drawable-mdpi/droid_watermark.png differ diff --git a/res/drawable-mdpi/group_chat.png b/res/drawable-mdpi/group_chat.png new file mode 100644 index 0000000..c08d822 Binary files /dev/null and b/res/drawable-mdpi/group_chat.png differ diff --git a/res/drawable-mdpi/group_chat_new.png b/res/drawable-mdpi/group_chat_new.png new file mode 100644 index 0000000..6490d77 Binary files /dev/null and b/res/drawable-mdpi/group_chat_new.png differ diff --git a/res/drawable-mdpi/ic_im_block.png b/res/drawable-mdpi/ic_im_block.png new file mode 100644 index 0000000..36b3c03 Binary files /dev/null and b/res/drawable-mdpi/ic_im_block.png differ diff --git a/res/drawable-mdpi/ic_im_message_not_sent.png b/res/drawable-mdpi/ic_im_message_not_sent.png new file mode 100644 index 0000000..477b575 Binary files /dev/null and b/res/drawable-mdpi/ic_im_message_not_sent.png differ diff --git a/res/drawable-mdpi/ic_launcher_im.png b/res/drawable-mdpi/ic_launcher_im.png new file mode 100644 index 0000000..afc35a2 Binary files /dev/null and b/res/drawable-mdpi/ic_launcher_im.png differ diff --git a/res/drawable-mdpi/ic_menu_my_profile.png b/res/drawable-mdpi/ic_menu_my_profile.png new file mode 100644 index 0000000..6f9caf7 Binary files /dev/null and b/res/drawable-mdpi/ic_menu_my_profile.png differ diff --git a/res/drawable-mdpi/ic_menu_view_profile.png b/res/drawable-mdpi/ic_menu_view_profile.png new file mode 100644 index 0000000..afc18aa Binary files /dev/null and b/res/drawable-mdpi/ic_menu_view_profile.png differ diff --git a/res/drawable-mdpi/im_avatar_picture_border_normal.9.png b/res/drawable-mdpi/im_avatar_picture_border_normal.9.png new file mode 100644 index 0000000..01cc9dc Binary files /dev/null and b/res/drawable-mdpi/im_avatar_picture_border_normal.9.png differ diff --git a/res/drawable-mdpi/im_logo.png b/res/drawable-mdpi/im_logo.png new file mode 100644 index 0000000..3bd8ca0 Binary files /dev/null and b/res/drawable-mdpi/im_logo.png differ diff --git a/res/drawable-mdpi/imlogo_s.png b/res/drawable-mdpi/imlogo_s.png new file mode 100644 index 0000000..b7aa43a Binary files /dev/null and b/res/drawable-mdpi/imlogo_s.png differ diff --git a/res/drawable-mdpi/list_item_im_bubble_default.9.png b/res/drawable-mdpi/list_item_im_bubble_default.9.png new file mode 100644 index 0000000..92f7058 Binary files /dev/null and b/res/drawable-mdpi/list_item_im_bubble_default.9.png differ diff --git a/res/drawable-mdpi/list_item_im_bubble_pressed.9.png b/res/drawable-mdpi/list_item_im_bubble_pressed.9.png new file mode 100644 index 0000000..2032bc0 Binary files /dev/null and b/res/drawable-mdpi/list_item_im_bubble_pressed.9.png differ diff --git a/res/drawable-mdpi/list_item_im_bubble_selected.9.png b/res/drawable-mdpi/list_item_im_bubble_selected.9.png new file mode 100644 index 0000000..bd0a1e2 Binary files /dev/null and b/res/drawable-mdpi/list_item_im_bubble_selected.9.png differ diff --git a/res/drawable-mdpi/picture_frame_background.9.png b/res/drawable-mdpi/picture_frame_background.9.png new file mode 100644 index 0000000..8ecddcd Binary files /dev/null and b/res/drawable-mdpi/picture_frame_background.9.png differ diff --git a/res/drawable-mdpi/status_chat.png b/res/drawable-mdpi/status_chat.png new file mode 100644 index 0000000..abfb6fa Binary files /dev/null and b/res/drawable-mdpi/status_chat.png differ diff --git a/res/drawable-mdpi/status_chat_new.png b/res/drawable-mdpi/status_chat_new.png new file mode 100644 index 0000000..564b38b Binary files /dev/null and b/res/drawable-mdpi/status_chat_new.png differ diff --git a/res/drawable-mdpi/text_divider_horizontal.9.png b/res/drawable-mdpi/text_divider_horizontal.9.png new file mode 100644 index 0000000..21b11a3 Binary files /dev/null and b/res/drawable-mdpi/text_divider_horizontal.9.png differ diff --git a/res/drawable-mdpi/textfield_im_pressed.9.png b/res/drawable-mdpi/textfield_im_pressed.9.png new file mode 100644 index 0000000..a768cb4 Binary files /dev/null and b/res/drawable-mdpi/textfield_im_pressed.9.png differ diff --git a/res/drawable-mdpi/textfield_im_received.9.png b/res/drawable-mdpi/textfield_im_received.9.png new file mode 100644 index 0000000..bde8fc3 Binary files /dev/null and b/res/drawable-mdpi/textfield_im_received.9.png differ diff --git a/res/drawable-mdpi/textfield_im_selected.9.png b/res/drawable-mdpi/textfield_im_selected.9.png new file mode 100644 index 0000000..ba958c8 Binary files /dev/null and b/res/drawable-mdpi/textfield_im_selected.9.png differ diff --git a/res/drawable/avatar_unknown.png b/res/drawable/avatar_unknown.png deleted file mode 100644 index 3a338e8..0000000 Binary files a/res/drawable/avatar_unknown.png and /dev/null differ diff --git a/res/drawable/background_textfield.9.png b/res/drawable/background_textfield.9.png deleted file mode 100644 index 679e00f..0000000 Binary files a/res/drawable/background_textfield.9.png and /dev/null differ diff --git a/res/drawable/dashboard_highlight_selected.9.png b/res/drawable/dashboard_highlight_selected.9.png deleted file mode 100644 index 473e059..0000000 Binary files a/res/drawable/dashboard_highlight_selected.9.png and /dev/null differ diff --git a/res/drawable/default_background.9.png b/res/drawable/default_background.9.png new file mode 100644 index 0000000..33cb551 Binary files /dev/null and b/res/drawable/default_background.9.png differ diff --git a/res/drawable/dialog_im_switch_chats_strip.9.png b/res/drawable/dialog_im_switch_chats_strip.9.png deleted file mode 100644 index 1fe2c11..0000000 Binary files a/res/drawable/dialog_im_switch_chats_strip.9.png and /dev/null differ diff --git a/res/drawable/droid_watermark.png b/res/drawable/droid_watermark.png deleted file mode 100644 index 785f7b9..0000000 Binary files a/res/drawable/droid_watermark.png and /dev/null differ diff --git a/res/drawable/group_chat.png b/res/drawable/group_chat.png deleted file mode 100644 index c08d822..0000000 Binary files a/res/drawable/group_chat.png and /dev/null differ diff --git a/res/drawable/group_chat_new.png b/res/drawable/group_chat_new.png deleted file mode 100644 index 6490d77..0000000 Binary files a/res/drawable/group_chat_new.png and /dev/null differ diff --git a/res/drawable/ic_im_block.png b/res/drawable/ic_im_block.png deleted file mode 100644 index 36b3c03..0000000 Binary files a/res/drawable/ic_im_block.png and /dev/null differ diff --git a/res/drawable/ic_im_message_not_sent.png b/res/drawable/ic_im_message_not_sent.png deleted file mode 100644 index 477b575..0000000 Binary files a/res/drawable/ic_im_message_not_sent.png and /dev/null differ diff --git a/res/drawable/ic_launcher_im.png b/res/drawable/ic_launcher_im.png deleted file mode 100644 index afc35a2..0000000 Binary files a/res/drawable/ic_launcher_im.png and /dev/null differ diff --git a/res/drawable/ic_menu_my_profile.png b/res/drawable/ic_menu_my_profile.png deleted file mode 100644 index 6f9caf7..0000000 Binary files a/res/drawable/ic_menu_my_profile.png and /dev/null differ diff --git a/res/drawable/ic_menu_view_profile.png b/res/drawable/ic_menu_view_profile.png deleted file mode 100644 index afc18aa..0000000 Binary files a/res/drawable/ic_menu_view_profile.png and /dev/null differ diff --git a/res/drawable/im_avatar_picture_border_normal.9.png b/res/drawable/im_avatar_picture_border_normal.9.png deleted file mode 100644 index 01cc9dc..0000000 Binary files a/res/drawable/im_avatar_picture_border_normal.9.png and /dev/null differ diff --git a/res/drawable/im_logo.png b/res/drawable/im_logo.png deleted file mode 100644 index 3bd8ca0..0000000 Binary files a/res/drawable/im_logo.png and /dev/null differ diff --git a/res/drawable/imlogo_s.png b/res/drawable/imlogo_s.png deleted file mode 100644 index b7aa43a..0000000 Binary files a/res/drawable/imlogo_s.png and /dev/null differ diff --git a/res/drawable/list_item_im_bubble_default.9.png b/res/drawable/list_item_im_bubble_default.9.png deleted file mode 100644 index 92f7058..0000000 Binary files a/res/drawable/list_item_im_bubble_default.9.png and /dev/null differ diff --git a/res/drawable/list_item_im_bubble_pressed.9.png b/res/drawable/list_item_im_bubble_pressed.9.png deleted file mode 100644 index 2032bc0..0000000 Binary files a/res/drawable/list_item_im_bubble_pressed.9.png and /dev/null differ diff --git a/res/drawable/list_item_im_bubble_selected.9.png b/res/drawable/list_item_im_bubble_selected.9.png deleted file mode 100644 index bd0a1e2..0000000 Binary files a/res/drawable/list_item_im_bubble_selected.9.png and /dev/null differ diff --git a/res/drawable/picture_frame_background.9.png b/res/drawable/picture_frame_background.9.png deleted file mode 100644 index 8ecddcd..0000000 Binary files a/res/drawable/picture_frame_background.9.png and /dev/null differ diff --git a/res/drawable/status_chat.png b/res/drawable/status_chat.png deleted file mode 100644 index abfb6fa..0000000 Binary files a/res/drawable/status_chat.png and /dev/null differ diff --git a/res/drawable/status_chat_new.png b/res/drawable/status_chat_new.png deleted file mode 100644 index 564b38b..0000000 Binary files a/res/drawable/status_chat_new.png and /dev/null differ diff --git a/res/drawable/text_divider_horizontal.9.png b/res/drawable/text_divider_horizontal.9.png deleted file mode 100644 index 21b11a3..0000000 Binary files a/res/drawable/text_divider_horizontal.9.png and /dev/null differ diff --git a/res/drawable/textfield_im_pressed.9.png b/res/drawable/textfield_im_pressed.9.png deleted file mode 100644 index a768cb4..0000000 Binary files a/res/drawable/textfield_im_pressed.9.png and /dev/null differ diff --git a/res/drawable/textfield_im_received.9.png b/res/drawable/textfield_im_received.9.png deleted file mode 100644 index bde8fc3..0000000 Binary files a/res/drawable/textfield_im_received.9.png and /dev/null differ diff --git a/res/drawable/textfield_im_selected.9.png b/res/drawable/textfield_im_selected.9.png deleted file mode 100644 index ba958c8..0000000 Binary files a/res/drawable/textfield_im_selected.9.png and /dev/null differ diff --git a/res/layout/account_view.xml b/res/layout/account_view.xml new file mode 100644 index 0000000..411c844 --- /dev/null +++ b/res/layout/account_view.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/contact_list_view.xml b/res/layout/contact_list_view.xml index 01e3b50..619dff1 100644 --- a/res/layout/contact_list_view.xml +++ b/res/layout/contact_list_view.xml @@ -76,6 +76,7 @@ + android:layout_height="fill_parent" + android:nextFocusUp="@id/statusDropDownButton" /> diff --git a/res/layout/contact_view.xml b/res/layout/contact_view.xml index b64d990..00dc0b9 100644 --- a/res/layout/contact_view.xml +++ b/res/layout/contact_view.xml @@ -58,7 +58,7 @@ android:layout_height="wrap_content"> + "čtení zpráv chatu" + "Povoluje aplikacím číst data od poskytovatele chatu." + "psaní zpráv chatu" + "Povoluje aplikacím psát data poskytovateli chatu." "Chat" + "Chat – vybrat účet" + "Přidat účet" + "Upravit účet" + "Odebrat účet" + "Odhlásit se ze všech služeb" + "Chat – vybrat účet" + "(%1$d)" "Zrušit přihlašování" "Přidat kontakt" "Smazat kontakt" @@ -34,6 +45,7 @@ "Menu+" "Vstup" "Potvrdit" + "Chcete se odhlásit ze všech služeb?" "Kontakt %1$s bude smazán." "Kontakt %1$s bude blokován." "Kontakt %1$s bude odblokován." diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index c83e272..07661dc 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -15,7 +15,18 @@ --> + "læs chatbeskeder" + "Tillader programmer at læse data fra IM-indholdsleverandøren." + "skriv chatbeskeder" + "Tillader programmer at skrive data til IM-indholdsleverandøren." "IM" + "Chat – Vælg en konto" + "Tilføj konto" + "Rediger konto" + "Fjern konto" + "Log alle ud" + "Chat – Vælg en konto" + "(%1$d)" "Annuller login" "Tilføj kontakt" "Slet kontakt" @@ -34,6 +45,7 @@ "Menu+" "Input" "Bekræft" + "Vil du logge ud af alle tjenester?" "Kontakten \"%1$s\" slettes." "Kontakten \"%1$s\" blokeres." "Blokeringen af kontakten \"%1$s\" ophæves." diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index d9aa1f9..749cb3a 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -15,7 +15,18 @@ --> + "Chat-Nachrichten lesen" + "Ermöglicht Anwendungen das Lesen von Daten des IM-Content-Providers." + "Chat-Nachrichten verfassen" + "Ermöglicht Anwendungen das Schreiben von Daten an den IM-Content-Provider." "Chat" + "Chat – Konto auswählen" + "Konto hinzufügen" + "Konto bearbeiten" + "Konto entfernen" + "Alle abmelden" + "Chat – Konto auswählen" + "(%1$d)" "Anmeldung abbrechen" "Kontakt hinzufügen" "Kontakt löschen" @@ -34,6 +45,7 @@ "Menü +" "Eingabe" "Bestätigen" + "Möchten Sie alle Dienste abmelden?" "Kontakt \"%1$s\" wird gelöscht." "Kontakt \"%1$s\" wird gesperrt." "Blockierung des Kontakts \"%1$s\" wird aufgehoben." diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index ade72df..e539860 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -15,7 +15,18 @@ --> + "ανάγνωση άμεσων μηνυμάτων" + "Επιτρέπει στις εφαρμογές την ανάγνωση δεδομένων από τον πάροχο περιεχομένου ανταλλαγής άμεσων μηνυμάτων (IM)." + "εγγραφή άμεσων μηνυμάτων" + "Επιτρέπει στις εφαρμογές την εγγραφή δεδομένων από τον πάροχο της υπηρεσίας άμεσων μηνυμάτων (IM)." "Ανταλ.άμεσων μην.(IM)" + "Συζήτηση - Επιλογή λογαριασμού" + "Προσθήκη λογαριασμού" + "Επεξεργασία λογαριασμού" + "Κατάργηση λογαριασμού" + "Αποσύνδεση όλων" + "Συζήτηση - Επιλογή λογαριασμού" + "(%1$d)" "Ακύρωση σύνδεσης" "Προσθήκη επαφής" "Διαγραφή επαφής" @@ -34,6 +45,7 @@ "Μενού+" "Είσοδος" "Επιβεβαίωση" + "Θέλετε να πραγματοποιήσετε αποσύνδεση από όλες τις υπηρεσίες;" "Η επαφή \"%1$s\" θα διαγραφεί." "Θα γίνει αποκλεισμός της επαφής \"%1$s\"." "Θα γίνει κατάργηση αποκλεισμού της επαφής \"%1$s\"." diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index 649d27b..6226606 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -15,7 +15,18 @@ --> + "leer mensajes instantáneos" + "Permite que las aplicaciones lean datos del proveedor de contenido de mensajería instantánea" + "escribir mensajes instantáneos" + "Permite que las aplicaciones ingresen datos en el proveedor de contenido de mensajería instantánea" "Mensajería instantánea" + "Chat - Seleccionar una cuenta" + "Agregar cuenta" + "Editar cuenta" + "Eliminar cuenta" + "Cerrar sesión de todo" + "Chat - Seleccionar una cuenta" + "(%1$d)" "Cancelar inicio de sesión" "Agregar contacto" "Eliminar contacto" @@ -34,6 +45,7 @@ "Menú+" "Entrada" "Confirmar" + "¿Deseas salir de todos los servicios?" "El contacto \"%1$s\" se eliminará." "El contacto \"%1$s\" se bloqueará." "El contacto \"%1$s\" se desbloqueará." diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 2b38f77..98c2e2e 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -15,7 +15,18 @@ --> + "leer mensajes instantáneos" + "Permite que las aplicaciones lean datos del proveedor de contenido de MI." + "escribir mensajes instantáneos" + "Permite que las aplicaciones escriban datos en el proveedor de contenido de MI." "MI" + "Chat: seleccionar una cuenta" + "Añadir cuenta" + "Editar cuenta" + "Eliminar cuenta" + "Salir de todo" + "Chat: seleccionar una cuenta" + "(%1$d)" "Cancelar acceso" "Añadir contacto" "Eliminar contacto" @@ -34,6 +45,7 @@ "MENU+" "Introducción de texto" "Confirmar" + "¿Quieres salir de todos los servicios?" "El contacto \"%1$s\" se eliminará." "El contacto \"%1$s\" se bloqueará." "El contacto \"%1$s\" se desbloqueará." diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 0c431bc..2226521 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -15,7 +15,18 @@ --> + "Lire des messages instantanés" + "Permet aux applications de lire les données du fournisseur de service de messagerie instantanée." + "Écrire des messages instantanés" + "Permet aux applications d\'écrire des données du fournisseur de service de messagerie instantanée." "Chat" + "Chat : sélectionner un compte" + "Ajouter un compte" + "Modifier un compte" + "Supprimer le compte" + "Se déconnecter de tout" + "Chat : sélectionner un compte" + "(%1$d)" "Annuler la connexion" "Ajouter un contact" "Supprimer le contact" @@ -34,6 +45,7 @@ "Menu+" "Entrée" "Confirmer" + "Voulez-vous vous déconnecter de tous les services ?" "Le contact \"%1$s\" sera supprimé." "Le contact \"%1$s\" sera bloqué." "Le contact \"%1$s\" sera débloqué." diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 999edba..6832ec5 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -15,7 +15,18 @@ --> + "lettura di messaggi immediati" + "Consente alle applicazioni di leggere dati del provider di contenuti IM." + "scrittura di messaggi immediati" + "Consente alle applicazioni di scrivere dati per il provider di contenuti IM." "Chat" + "Chat - Seleziona un account" + "Aggiungi account" + "Modifica account" + "Rimuovi account" + "Esci da tutto" + "Chat - Seleziona un account" + "(%1$d)" "Annulla accesso" "Aggiungi contatto" "Elimina contatto" @@ -34,6 +45,7 @@ "Menu+" "Input" "Conferma" + "Uscire da tutti i servizi?" "Il contatto \"%1$s\" sarà eliminato." "Il contatto \"%1$s\" sarà bloccato." "Il contatto \"%1$s\" sarà sbloccato." diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 954cffc..61deb67 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -15,7 +15,18 @@ --> + "インスタントメッセージを表示" + "アプリケーションでIMコンテンツプロバイダからデータを読み取れるようにします。" + "インスタントメッセージを作成" + "アプリケーションからIMコンテンツプロバイダにデータを書き込めるようにします。" "チャット" + "チャット: アカウントを選択" + "アカウントを追加" + "アカウントを編集" + "アカウントを削除" + "すべてログアウト" + "チャット: アカウントを選択" + "(%1$d件)" "ログインをキャンセル" "連絡先を追加" "連絡先を削除" @@ -34,6 +45,7 @@ "Menu+" "入力" "確認" + "すべてのアカウントからログアウトしますか?" "連絡先「%1$s」を削除します。" "連絡先「%1$s」をブロックします。" "連絡先「%1$s」のブロックを解除します。" diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index b92adca..cf98293 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -15,7 +15,18 @@ --> + "인스턴트 메시지 읽기" + "응용프로그램이 메신저 콘텐츠 제공업체에서 제공한 데이터를 읽을 수 있도록 합니다." + "인스턴트 메시지 쓰기" + "응용프로그램이 메신저 콘텐츠 제공업체에 데이터를 쓸 수 있도록 합니다." "메신저" + "채팅 - 계정 선택" + "계정 추가" + "계정 수정" + "계정 삭제" + "모두 로그아웃" + "채팅 - 계정 선택" + "(%1$d)" "로그인 취소" "연락처 추가" "연락처 삭제" @@ -34,6 +45,7 @@ "Menu+" "입력" "확인" + "모든 서비스에서 로그아웃하시겠습니까?" "연락처 \'%1$s\'이(가) 삭제됩니다." "연락처 \'%1$s\'이(가) 차단됩니다." "연락처 \'%1$s\'이(가) 차단 해제됩니다." diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index d0b10cf..2ce18fd 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -15,8 +15,19 @@ --> + "lese nettpratmeldinger" + "Lar applikasjoner lese data fra innholdsleverandøren for nettprat." + "skrive nettpratmeldinger" + "Lar applikasjoner skrive data til innholdsleverandøren for nettprat." "Nettprat" - "Avbryt innlogging" + "Nettprat – velg en konto" + "Legg til konto" + "Rediger konto" + "Fjern konto" + "Logg av alle" + "Nettprat – velg en konto" + "(%1$d)" + "Avbryt pålogging" "Legg til kontakt" "Fjern kontakt" "Blokker kontakt" @@ -24,7 +35,7 @@ "Kontoliste" "Innstillinger" "Start samtale" - "Logg ut" + "Logg av" "Vis profil" "Avslutt samtale" "Kontaktliste" @@ -34,6 +45,7 @@ "Meny+" "Inndata" "Bekreft" + "Vil du logge deg av alle tjenester?" "Kontakten «%1$s» vil bli slettet." "Kontakten «%1$s» vil bli blokkert." "Kontakten «%1$s» vil bli avblokkert." @@ -41,18 +53,18 @@ "Avbryt" "OK" "Avbryt" - "De er blitt logget ut fra %1$s." - "Du er blitt logget ut fra %1$s fordi %2$s." + "De er blitt logget av %1$s." + "Du er blitt logget av %1$s fordi %2$s." "Legg til %1$s-konto" "Brukernavn:" "Passord:" "Husk passord." - "Logg inn automatisk." + "Logg på automatisk." "Mangler du konto?" - "Dette valget logger deg automatisk inn hver gang du åpner applikasjonen. For å avmerke valget, logg ut og fjern så haken ved «Logg inn automatisk»." - "Logg inn" - "Logger inn på %1$s" - "Logger inn…" + "Dette valget logger deg automatisk på hver gang du åpner applikasjonen. For å avmerke valget, logg av og fjern så haken ved «Logg på automatisk»." + "Logg på" + "Logger på %1$s" + "Logger på…" "Bakgrunnsdata deaktivert" "%1$s trenger at bakgrunnsdata er aktivert." "Aktiver" @@ -77,7 +89,7 @@ "Opptatt" "Borte" "Inaktiv" - "Avlogget" + "Frakoblet" "Usynlig" "Samtale med %1$s" "Meg" @@ -85,16 +97,16 @@ "%1$s er tilgjengelig" "%1$s er borte" "%1$s er opptatt" - "%1$s er avlogget" + "%1$s er frakoblet" "%1$s har blitt med i samtalen" "%1$s har forlatt samtalen" "'Sendt 'hh':'mm' 'a', 'EEEE" "Send" "Kunne ikke sende meldingen." "Mistet tilkoblingen til tjeneren. Meldingen vil bli sendt ved tilkobling." - "%1$s er avlogget. Meldinger du sender vil bli levert når %1$s kobler til." + "%1$s er frakoblet. Meldinger du sender vil bli levert når %1$s kobler til." "%1$s er ikke i kontaktlisten." - "Velg lenke" + "Velg kobling" "Ingen aktive samtaler." "Legg til kontakt" "E-postadresse til den du ønsker å invitere:" @@ -132,7 +144,7 @@ "start lynmeldingstjeneste" "Tillater applikasjoner å starte lynmeldingstjenesten." "NB" - "Kunne ikke logge inn på %1$s. Prøv igjen senere."\n"(Detaljer: %2$s)" + "Kunne ikke logge på %1$s. Prøv igjen senere."\n"(Detaljer: %2$s)" "Listen ble ikke lagt til." "Kontakten ble ikke blokkert." "Kontakten ble ikke avblokkert." @@ -156,11 +168,11 @@ "Tjeneren støtter ikke videresending til domenet." "Brukernavnet du skrev inn ble ikke gjenkjent." "Beklager, du er blokkert av brukeren." - "Sesjonen har gått ut, logg inn på nytt." - "du er logget inn på en annen klient." - "du allerede er logget inn på en annen klient." + "Sesjonen har gått ut, logg på på nytt." + "du er pålogget med en annen klient." + "du allerede er pålogget med en annen klient." "Beklager, kan ikke lese telefonnummeret fra SIM-kortet. Kontakt operatøren for hjelp." - "Du er ikke logget inn." + "Du er ikke pålogget." "Feilkode %1$d" "Glad" diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 9f635a5..a6e115f 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -15,7 +15,18 @@ --> + "chatberichten lezen" + "Toepassingen toestaan gegevens te lezen van de provider van chatinhoud." + "chatberichten schrijven" + "Toepassingen toestaan gegevens te schrijven naar de provider van chatinhoud." "Chat" + "Chatten - Een account selecteren" + "Account toevoegen" + "Account bewerken" + "Account verwijderen" + "Overal afmelden" + "Chatten - Een account selecteren" + "(%1$d)" "Aanmelding annuleren" "Contact toevoegen" "Contact verwijderen" @@ -34,6 +45,7 @@ "Menu+" "Invoer" "Bevestigen" + "Wilt u zich afmelden bij alle services?" "Contact \'%1$s\' wordt verwijderd." "Contact \'%1$s\' wordt geblokkeerd." "Contact \'%1$s\' wordt gedeblokkeerd." diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index be9e375..0da89a3 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -15,7 +15,18 @@ --> + "odczytaj wiadomości czatu" + "Zezwala aplikacjom na odczytywanie danych z dostawcy zawartości czatu." + "zapisz wiadomości czatu" + "Zezwala aplikacjom na zapisywanie danych do dostawcy zawartości czatu." "Czat" + "Czat: wybierz konto" + "Dodaj konto" + "Edytuj konto" + "Usuń konto" + "Wyloguj się ze wszystkich" + "Czat: wybierz konto" + "(%1$d)" "Anuluj logowanie" "Dodaj kontakt" "Usuń kontakt" @@ -34,6 +45,7 @@ "Menu+" "Katalog wejściowy" "Potwierdź" + "Czy chcesz wylogować się ze wszystkich usług?" "Kontakt „%1$s” zostanie usunięty." "Kontakt „%1$s” zostanie zablokowany." "Kontakt „%1$s” zostanie odblokowany." diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index a511d55..fa04a40 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -15,7 +15,18 @@ --> + "ler mensagens instantâneas" + "Permite que as aplicações leiam dados a partir do fornecedor de conteúdo de MI." + "escrever mensagens instantâneas" + "Permite que as aplicações escrevam dados para o fornecedor de conteúdo de MI." "MI" + "Chat - Seleccione uma conta" + "Adicionar conta" + "Editar conta" + "Remover conta" + "Terminar sessão em todos" + "Chat - Seleccione uma conta" + "(%1$d)" "Cancelar início de sessão" "Adicionar contacto" "Eliminar contacto" @@ -34,6 +45,7 @@ "Menu+" "Entrada" "Confirmar" + "Pretende terminar sessão em todos os serviços?" "O contacto \"%1$s\" será eliminado." "O contacto \"%1$s\" será bloqueado." "O contacto \"%1$s\" será desbloqueado." diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index bd334a1..a01bb6b 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -15,7 +15,18 @@ --> + "ler mensagens instantâneas" + "Permite que os aplicativos leiam os dados do provedor de conteúdo de mensagem instantânea." + "gravar mensagens instantâneas" + "Permite que os aplicativos gravem os dados no provedor de conteúdo de mensagem instantânea." "Mensagem instantânea" + "Bate-papo - Selecione uma conta" + "Adicionar conta" + "Editar conta" + "Remover conta" + "Sair de todos" + "Bate-papo - Selecione uma conta" + "(%1$d)" "Cancelar login" "Adicionar contato" "Excluir contato" @@ -34,6 +45,7 @@ "Menu+" "Entrada" "Confirmar" + "Deseja sair de todos os serviços?" "O contato \"%1$s\" será excluído." "O contato \"%1$s\" será bloqueado." "O contato \"%1$s\" será desbloqueado." diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index a0e029d..c243a82 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -15,7 +15,18 @@ --> + "читать мгновенные сообщения" + "Позволяет приложениям считывать данные из провайдера чата." + "писать мгновенные сообщения" + "Позволяет приложениям отправлять данные провайдеру чата." "Чат" + "Чат – выберите аккаунт" + "Добавить аккаунт" + "Изменить аккаунт" + "Удалить аккаунт" + "Выйти отовсюду" + "Чат – выберите аккаунт" + "(%1$d)" "Отмена входа" "Добавить контакт" "Удалить контакт" @@ -34,6 +45,7 @@ "Мenu+" "Ввод" "Подтвердить" + "Выйти из всех служб?" "Контакт \"%1$s\" будет удален." "Контакт \"%1$s\" будет заблокирован." "Контакт \"%1$s\" будет разблокирован." diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index d3bb2b8..ed23f4e 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -15,7 +15,18 @@ --> + "läsa snabbmeddelanden" + "Tillåter att ett program läser data från snabbmeddelandeleverantören." + "skriva snabbmeddelanden" + "Tillåter att ett program skriver data till snabbmeddelandeleverantören." "Chatt" + "Chatt – välj ett konto" + "Lägg till konto" + "Redigera konto" + "Ta bort konto" + "Logga ut från alla" + "Chatt – välj ett konto" + "(%1$d)" "Avbryt inloggning" "Lägg till kontakt" "Ta bort kontakt" @@ -34,6 +45,7 @@ "Meny+" "Indata" "Bekräfta" + "Vill du logga ut från alla tjänster?" "Kontakten %1$s tas bort." "Kontakten %1$s blockeras." "Blockering hävs för kontakten %1$s." diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index d0e2c8f..420fdc2 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -15,7 +15,18 @@ --> + "anlık iletileri oku" + "Uygulamalara, IM içerik sağlayıcısından gelen verileri okuma izni verir." + "anlık iletiler yazın" + "Uygulamalara, IM içerik sağlayıcısına veri yazma izni verir." "IM" + "Sohbet - Bir hesap seçin" + "Hesap ekle" + "Hesabı düzenle" + "Hesabı kaldır" + "Tüm hizmetlerden çık" + "Sohbet - Bir hesap seçin" + "(%1$d)" "Oturum açmayı iptal et" "Kişi ekle" "Kişiyi sil" @@ -34,6 +45,7 @@ "Menü+" "Giriş" "Doğrula" + "Tüm hizmetlerden çıkmak istiyor musunuz?" "\"%1$s\" kişisi silinecek." "\"%1$s\" kişisi engellenecek." "\"%1$s\" kişisinin engellemesi kaldırılacak." diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 42c2c16..ef7ae12 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -15,7 +15,18 @@ --> + "阅读即时消息" + "允许应用程序从即时消息内容提供者处读取数据。" + "撰写即时消息" + "允许应用程序向即时消息内容提供者写入数据。" "即时消息" + "聊天 - 选择一个帐户" + "添加帐户" + "编辑帐户" + "删除帐户" + "全部退出" + "聊天 - 选择一个帐户" + "(%1$d)" "取消登录" "添加联系人" "删除联系人" @@ -34,6 +45,7 @@ "MENU+" "输入" "确认" + "是否要退出全部服务?" "将会删除联系人“%1$s”。" "将会阻止联系人“%1$s”。" "将会解除阻止联系人“%1$s”。" @@ -53,7 +65,7 @@ "登录" "正在登录 %1$s" "正在登录..." - "背景数据已禁用" + "已停用背景数据" "%1$s 需要启用背景数据。" "启用" "退出" diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index d644a6e..5ecd31f 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -15,7 +15,18 @@ --> + "讀取即時訊息" + "允許應用程式讀取來自即時訊息內容提供端的資料。" + "撰寫即時訊息" + "允許應用程式將資料寫入即時訊息內容提供端。" "即時訊息" + "即時通訊 - 選取帳戶" + "新增帳戶" + "編輯帳戶" + "移除帳戶" + "全部登出" + "即時通訊 - 選取帳戶" + "(%1$d)" "取消登入" "新增聯絡人" "刪除聯絡人" @@ -34,6 +45,7 @@ "[Menu] +" "輸入" "確認" + "您要登出所有服務嗎?" "刪除「%1$s」?" "封鎖「%1$s」。" "「%1$s」會被解除封鎖。" diff --git a/res/values/strings.xml b/res/values/strings.xml index fb366b8..3fe363a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -18,10 +18,40 @@ */ --> + read instant messages + + Allows applications to read data from the IM content provider. + + + write instant messages + + Allows applications to write data to the IM content provider. + + IM + + + Chat - Select an account + + + + Add account + + Edit account + + Remove account + + Sign out all + + + + Chat - Select an account + + (%1$d) + Cancel signin @@ -86,6 +116,8 @@ Confirm + + Do you want to sign out all services? The contact \"%1$s\" will be deleted. diff --git a/samples/PluginDemo/Android.mk b/samples/PluginDemo/Android.mk deleted file mode 100644 index 46fe534..0000000 --- a/samples/PluginDemo/Android.mk +++ /dev/null @@ -1,12 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := samples - -LOCAL_SRC_FILES := $(call all-subdir-java-files) \ - -LOCAL_PACKAGE_NAME := ImPluginDemo - -LOCAL_JAVA_LIBRARIES := com.android.im.plugin - -include $(BUILD_PACKAGE) diff --git a/samples/PluginDemo/AndroidManifest.xml b/samples/PluginDemo/AndroidManifest.xml deleted file mode 100644 index eaebd68..0000000 --- a/samples/PluginDemo/AndroidManifest.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/samples/PluginDemo/res/drawable/chat.png b/samples/PluginDemo/res/drawable/chat.png deleted file mode 100755 index be844fd..0000000 Binary files a/samples/PluginDemo/res/drawable/chat.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/chat_new.png b/samples/PluginDemo/res/drawable/chat_new.png deleted file mode 100755 index f31ba59..0000000 Binary files a/samples/PluginDemo/res/drawable/chat_new.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_angel.png b/samples/PluginDemo/res/drawable/emo_im_angel.png deleted file mode 100644 index c34dfa6..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_angel.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_cool.png b/samples/PluginDemo/res/drawable/emo_im_cool.png deleted file mode 100644 index d8eeb34..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_cool.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_crying.png b/samples/PluginDemo/res/drawable/emo_im_crying.png deleted file mode 100644 index 1cafdb3..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_crying.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_embarrassed.png b/samples/PluginDemo/res/drawable/emo_im_embarrassed.png deleted file mode 100644 index e4db963..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_embarrassed.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_foot_in_mouth.png b/samples/PluginDemo/res/drawable/emo_im_foot_in_mouth.png deleted file mode 100644 index 09d1fba..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_foot_in_mouth.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_happy.png b/samples/PluginDemo/res/drawable/emo_im_happy.png deleted file mode 100644 index b86602a..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_happy.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_kissing.png b/samples/PluginDemo/res/drawable/emo_im_kissing.png deleted file mode 100644 index 56378f6..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_kissing.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_laughing.png b/samples/PluginDemo/res/drawable/emo_im_laughing.png deleted file mode 100644 index 980bf28..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_laughing.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_lips_are_sealed.png b/samples/PluginDemo/res/drawable/emo_im_lips_are_sealed.png deleted file mode 100644 index f2de993..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_lips_are_sealed.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_money_mouth.png b/samples/PluginDemo/res/drawable/emo_im_money_mouth.png deleted file mode 100644 index 08c53fd..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_money_mouth.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_sad.png b/samples/PluginDemo/res/drawable/emo_im_sad.png deleted file mode 100644 index 31c08d0..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_sad.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_surprised.png b/samples/PluginDemo/res/drawable/emo_im_surprised.png deleted file mode 100644 index abe8c7a..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_surprised.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_tongue_sticking_out.png b/samples/PluginDemo/res/drawable/emo_im_tongue_sticking_out.png deleted file mode 100644 index 6f0f47b..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_tongue_sticking_out.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_undecided.png b/samples/PluginDemo/res/drawable/emo_im_undecided.png deleted file mode 100644 index eb4f8c5..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_undecided.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_winking.png b/samples/PluginDemo/res/drawable/emo_im_winking.png deleted file mode 100644 index 568562a..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_winking.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_wtf.png b/samples/PluginDemo/res/drawable/emo_im_wtf.png deleted file mode 100644 index 41dd47f..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_wtf.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/emo_im_yelling.png b/samples/PluginDemo/res/drawable/emo_im_yelling.png deleted file mode 100644 index c3c8612..0000000 Binary files a/samples/PluginDemo/res/drawable/emo_im_yelling.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/im_logo.png b/samples/PluginDemo/res/drawable/im_logo.png deleted file mode 100644 index 764d189..0000000 Binary files a/samples/PluginDemo/res/drawable/im_logo.png and /dev/null differ diff --git a/samples/PluginDemo/res/drawable/im_logo_splashscr.png b/samples/PluginDemo/res/drawable/im_logo_splashscr.png deleted file mode 100644 index 1b7ee6f..0000000 Binary files a/samples/PluginDemo/res/drawable/im_logo_splashscr.png and /dev/null differ diff --git a/samples/PluginDemo/res/values-cs/strings.xml b/samples/PluginDemo/res/values-cs/strings.xml deleted file mode 100644 index 0345b89..0000000 --- a/samples/PluginDemo/res/values-cs/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Přidat kontakt" - "Smazat kontakt" - "Blokovat" - "Seznam kontaktů" - "Odeslat zprávu" - "Info o kamarádovi" - "Konec konverzace" - "Přepnout chat" - "Vložit emotikony" - "Zřízení nového účtu" - "Pokud je váš telefon ukraden nebo jej ztratíte, přejděte v zájmu vlastní bezpečnosti na počítači na web a změňte své heslo." - "Seznam kontaktů – %1$s" - "Uživatelské jméno:" - "Konverzace (%1$d)" - "Kontaktní informace" - "Povoleno" - "Přidat kontakt" - "Jméno osoby, kterou chcete přidat:" - "Přidat kamaráda" - - "Happy" - "Smutný" - "Mrkající" - "Vypláznutý jazyk" - "Překvapený" - "Pusa" - "Hej!" - "Cool" - "Cinkání zlaťáků" - "Šlápota v úsměvu" - "V rozpacích" - "Andílek" - "Nerozhodný" - "Rozplakaný" - "Ani muk" - "Smějící se" - "Zmatený" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-da/strings.xml b/samples/PluginDemo/res/values-da/strings.xml deleted file mode 100644 index f1c5a2f..0000000 --- a/samples/PluginDemo/res/values-da/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Tilføj kontakt" - "Slet kontakt" - "Bloker" - "Liste over kontakter" - "Send IM" - "Venneoplysninger" - "Afslut samtale" - "Skift chats" - "Indsæt humørikoner" - "Få en ny konto" - "For din egen sikkerheds skyld skal du gå til webstedet på din computer og ændre din adgangskode, hvis du mister din telefon, eller den bliver stjålet." - "Liste over kontakter – %1$s" - "Brugernavn:" - "Samtaler (%1$d)" - "Kontaktoplysninger" - "Ledig" - "Tilføj kontakt" - "Skærmnavn for den person du ønsker at tilføje:" - "Tilføj ven" - - "Glad" - "Trist" - "Blinker" - "Rækker tunge" - "Overrasket" - "Kysser" - "Råber" - "Sej" - "Pengeglad" - "Forlegen" - "Flov" - "Engel" - "Uafklaret" - "Græder" - "Lukket med syv segl" - "Griner" - "Forvirret" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-de/strings.xml b/samples/PluginDemo/res/values-de/strings.xml deleted file mode 100644 index 5a081cb..0000000 --- a/samples/PluginDemo/res/values-de/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Kontakt hinzufügen" - "Kontakt löschen" - "Blockieren" - "Kontaktliste" - "IM senden" - "Buddy Info" - "Gespräch beenden" - "Chats wechseln" - "Emoticons einfügen" - "Ein neues Konto erhalten" - "Zu Ihrer Sicherheit: Wenn Sie Ihr Telefon verloren haben oder es gestohlen wurde, rufen Sie die Website auf Ihrem Computer auf und ändern Sie Ihr Passwort." - "Kontaktliste - %1$s" - "Benutzername:" - "Gespräche (%1$d)" - "Kontaktinformationen" - "Verfügbar" - "Kontakt hinzufügen" - "Displayname der Person, die Sie hinzufügen möchten:" - "Buddy hinzufügen" - - "Glücklich" - "Traurig" - "Zwinkern" - "Zunge rausstrecken" - "Überrascht" - "Kuss" - "Schreien" - "Cool" - "Lass Taten sprechen" - "Fettnäpfchen" - "Peinlich berührt" - "Engel" - "Unentschlossen" - "Weinen" - "Versiegelte Lippen" - "Lachen" - "Verwirrt" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-el/strings.xml b/samples/PluginDemo/res/values-el/strings.xml deleted file mode 100644 index 28501ae..0000000 --- a/samples/PluginDemo/res/values-el/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Προσθήκη επαφής" - "Διαγραφή επαφής" - "Αποκλεισμός" - "Λίστα επαφών" - "Αποστολή άμεσων μηνυμάτων (IM)" - "Πληροφορίες φίλου" - "Τερματισμός συνομιλίας" - "Αλλαγή συζητήσεων" - "Εισαγωγή εικονιδίων emoticons" - "Αποκτήστε νέο λογαριασμό" - "Για την ασφάλειά σας, εάν το τηλέφωνό σας χαθεί ή κλαπεί, μεταβείτε στον ιστότοπο στον υπολογιστή σας και αλλάξτε τον κωδικό πρόσβασης." - "Λίστα επαφών - %1$s" - "Όνομα χρήστη:" - "Συνομιλίες (%1$d)" - "Πληροφορίας επικοινωνίας" - "Διαθέσιμος/η" - "Προσθήκη επαφής" - "Το ψευδώνυμο του ατόμου που θέλετε να προσθέσετε:" - "Προσθήκη φίλου" - - "Είμαι χαρούμενος" - "Είμαι λυπημένος" - "Κλείνω το μάτι" - "Κοροϊδεύω" - "Είμαι έκπληκτος" - "Φιλάω" - "Φωνάζω" - "Cool" - "Κάνω τα λόγια μου πράξη" - "Είπα ανοησία" - "Ντρέπομαι" - "Αγγελούδι" - "Δεν έχω αποφασίσει" - "Κλαίω" - "Δεν αποκαλύπτω τίποτα" - "Γελάω" - "Είμαι μπερδεμένος" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-es-rUS/strings.xml b/samples/PluginDemo/res/values-es-rUS/strings.xml deleted file mode 100644 index c573baa..0000000 --- a/samples/PluginDemo/res/values-es-rUS/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Agregar contacto" - "Eliminar contacto" - "Bloquear" - "Lista de contactos" - "Enviar mensajería instantánea" - "Información de Buddy" - "Finalizar conversación" - "Modificar chats" - "Insertar emoticones" - "Obtén una cuenta nueva" - "Para tu seguridad, si pierdes o te roban el teléfono, ingresa al sitio web desde tu computadora y cambia la contraseña." - "Lista de contactos - %1$s" - "Nombre de usuario:" - "Conversaciones (%1$d)" - "Información de contacto" - "Disponible" - "Agregar contacto" - "Nombre de pantalla de la persona que deseas agregar:" - "Agregar Buddy" - - "Feliz" - "Triste" - "Guiñando un ojo" - "Con la lengua afuera" - "Sorprendido" - "Besando" - "Gritando" - "En la onda" - "Dinero en boca" - "Meter la pata" - "Avergonzado" - "Ángel" - "Indeciso" - "Llorando" - "Los labios están cerrados" - "Riendo" - "Confundido" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-es/strings.xml b/samples/PluginDemo/res/values-es/strings.xml deleted file mode 100644 index 0fcf217..0000000 --- a/samples/PluginDemo/res/values-es/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Añadir contacto" - "Eliminar contacto" - "Bloquear" - "Lista de contactos" - "Enviar MI" - "Información de amigo" - "Finalizar conversación" - "Cambiar de chat" - "Insertar emoticonos" - "Obtener una cuenta nueva" - "Si pierdes el teléfono o te lo roban, te recomendamos por seguridad que accedas al sitio web desde tu equipo y cambies la contraseña." - "Lista de contactos - %1$s" - "Nombre de usuario:" - "Conversaciones (%1$d)" - "Información de contacto" - "Disponible" - "Añadir contacto" - "Nombre de pantalla de la persona que quieres añadir:" - "Añadir amigo" - - "Contento" - "Triste" - "Guiño" - "Sacando la lengua" - "Sorprendido" - "Besando" - "Gritando" - "Atractivo" - "Dinero en la boca" - "Metedura de pata" - "Avergonzado" - "Ángel" - "Indeciso" - "Llorando" - "Labios sellados" - "Riendo" - "Confuso" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-fr/strings.xml b/samples/PluginDemo/res/values-fr/strings.xml deleted file mode 100644 index cc048ed..0000000 --- a/samples/PluginDemo/res/values-fr/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Ajouter un contact" - "Supprimer un contact" - "Bloquer" - "Liste de contacts" - "Envoyer un message instantané" - "Infos sur le contact" - "Terminer la conversation" - "Changer de chat" - "Insérer une émoticône" - "Créer un nouveau compte" - "Pour votre sécurité, en cas de vol ou de perte de votre téléphone, rendez-vous sur le site Web depuis votre ordinateur et modifiez votre mot de passe." - "Liste de contacts :%1$s" - "Nom d\'utilisateur :" - "Conversations (%1$d)" - "Infos sur le contact" - "Disponible" - "Ajouter le contact" - "Identifiant du contact que vous souhaitez ajouter :" - "Ajouter un contact" - - "Content" - "Triste" - "Clin d\'œil" - "Tire la langue" - "Surpris" - "Bisou" - "Hurle" - "Cool" - "Argent" - "Embarrassé" - "Gêné" - "Ange" - "Indécis" - "Pleure" - "Motus et bouche cousue" - "Rigole" - "Confus" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-it/strings.xml b/samples/PluginDemo/res/values-it/strings.xml deleted file mode 100644 index 6745c76..0000000 --- a/samples/PluginDemo/res/values-it/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Aggiungi contatto" - "Elimina contatto" - "Blocca" - "Elenco contatti" - "Invia msg chat" - "Info amico" - "Termina conversazione" - "Cambia conversazione" - "Inserisci emoticon" - "Ottieni un nuovo account" - "Per la tua sicurezza, in caso di perdita o furto del telefono visita il sito web da un computer e cambia la password." - "Elenco contatti - %1$s" - "Nome utente:" - "Conversazioni (%1$d)" - "Info contatto" - "Disponibile" - "Aggiungi contatto" - "Nome visualizzato della persona da aggiungere:" - "Aggiungi amico" - - "Felice" - "Triste" - "Occhiolino" - "Linguaccia" - "Sorpreso" - "Bacio" - "Urlo" - "Fico" - "Fatti, non parole" - "Gaffe" - "Imbarazzato" - "Angelo" - "Indeciso" - "Piango" - "Labbra cucite" - "Risata" - "Confuso" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-ja/strings.xml b/samples/PluginDemo/res/values-ja/strings.xml deleted file mode 100644 index f59333d..0000000 --- a/samples/PluginDemo/res/values-ja/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "連絡先を追加" - "連絡先を削除" - "ブロック" - "連絡先リスト" - "チャットを送信" - "友だち情報" - "チャット終了" - "チャットを切り替え" - "絵文字を挿入" - "新しいアカウントを取得" - "セキュリティ保護のため、携帯電話を紛失したり盗まれたりした場合は、パソコンからウェブサイトにアクセスしてパスワードを変更してください。" - "連絡先リスト - %1$s" - "ユーザー名:" - "チャット (%1$d件)" - "連絡先情報" - "オンライン" - "連絡先を追加" - "追加する人のスクリーンネーム:" - "友だちを追加" - - "ハッピー" - "悲しい" - "ウィンク" - "アッカンベー" - "びっくり" - "キス" - "激怒" - "クール" - "気持ち悪い" - "しまった" - "恥ずかしい" - "天使" - "迷う" - "泣く" - "お口にチャック" - "笑顔" - "混乱" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-ko/strings.xml b/samples/PluginDemo/res/values-ko/strings.xml deleted file mode 100644 index ab5cb9f..0000000 --- a/samples/PluginDemo/res/values-ko/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "연락처 추가" - "연락처 삭제" - "차단" - "연락처 목록" - "채팅하기" - "친구 정보" - "대화 종료" - "채팅 전환" - "이모티콘 삽입" - "새 계정 만들기" - "휴대전화를 잃어버렸거나 도난당한 경우에는 보안을 위해 컴퓨터에서 웹사이트를 방문하여 비밀번호를 변경하세요." - "연락처 목록 - %1$s" - "사용자 이름:" - "대화(%1$d개)" - "연락처 정보" - "온라인" - "연락처 추가" - "추가할 사람의 대화명:" - "친구 추가" - - "행복해" - "슬퍼요" - "윙크" - "메롱" - "헉!" - "키스" - "아악" - "멋지네" - "으이구" - "실수했네" - "당황" - "천사" - "결정하기 어렵군" - "엉엉엉" - "비밀로 할게" - "하하하" - "어리둥절" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-nb/strings.xml b/samples/PluginDemo/res/values-nb/strings.xml deleted file mode 100644 index 43d614a..0000000 --- a/samples/PluginDemo/res/values-nb/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Legg til kontakt" - "Fjern kontakt" - "Blokker kontakt" - "Kontaktliste" - "Start samtale" - "Vis profil" - "Avslutt samtale" - "Bytt mellom samtaler" - "Sett inn smilefjes" - "Mangler du konto?" - "Av sikkerhetsgrunner, gå til nettstedet og endre passordet om telefonen blir tapt eller stjåler." - "Kontaktliste - %1$s" - "Brukernavn:" - "Pågående samtaler (%1$d)" - "Kontaktprofil" - "Tilgjengelig" - "Legg til kontakt" - "E-postadresse til den du ønsker å invitere:" - "Send invitasjon" - - "Glad" - "Trist" - "Blunker" - "Rekker tunge" - "Overrasket" - "Kyss" - "Roper" - "Kul" - "Pengemunn" - "Fot i munnen" - "Flau" - "Engel" - "Usikker" - "Gråter" - "Stille som graven" - "Ler" - "Forvirret" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-nl/strings.xml b/samples/PluginDemo/res/values-nl/strings.xml deleted file mode 100644 index ea64f04..0000000 --- a/samples/PluginDemo/res/values-nl/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Contact toevoegen" - "Contact verwijderen" - "Blokkeren" - "Lijst met contacten" - "Chat verzenden" - "Buddygegevens" - "Conversatie beëindigen" - "Schakelen tussen chats" - "Emoticons invoegen" - "Een nieuw account verkrijgen" - "Als u uw telefoon verliest of als deze wordt gestolen, moet u voor uw veiligheid naar de website gaan op uw computer en uw wachtwoord wijzigen." - "Lijst met contacten - %1$s" - "Gebruikersnaam:" - "Conversaties (%1$d)" - "Contactgegevens" - "Beschikbaar" - "Contact toevoegen" - "Schermnaam van de persoon die u wilt toevoegen:" - "Buddy toevoegen" - - "Blij" - "Bedroefd" - "Knipoog" - "Tong uitsteken" - "Verrast" - "Kussend" - "Schreeuwend" - "Cool" - "Geldzoeker" - "Mond vol tanden" - "Beschaamd" - "Engel" - "Twijfelend" - "Huilend" - "Lippen op elkaar" - "Lachend" - "Verward" - - - ":-)" - ":-(" - ";-)" - ":-P" - "#NAME?" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-pl/strings.xml b/samples/PluginDemo/res/values-pl/strings.xml deleted file mode 100644 index bc254bf..0000000 --- a/samples/PluginDemo/res/values-pl/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Dodaj kontakt" - "Usuń kontakt" - "Zablokuj" - "Lista kontaktów" - "Wyślij wiadomość przez czat" - "Informacje o znajomym" - "Zakończ rozmowę" - "Przełącz czaty" - "Wstaw buźkę" - "Utwórz nowe konto" - "Ze względów bezpieczeństwa w przypadku zgubienia lub kradzieży telefonu odwiedź witrynę internetową ze swojego komputera i zmień hasło." - "Lista kontaktów – %1$s" - "Nazwa użytkownika:" - "Rozmowy: %1$d" - "Informacje kontaktowe" - "Dostępny" - "Dodaj kontakt" - "Widoczna nazwa dodawanej osoby:" - "Dodaj znajomego" - - "Wesoły" - "Smutny" - "Mruga" - "Pokazuje język" - "Zdziwienie" - "Całuje" - "Krzyczy" - "Na luzie" - "Pazerny" - "Nietaktowny" - "Zakłopotanie" - "Anioł" - "Niezdecydowany" - "Płacze" - "Milczy jak grób" - "Śmieje się" - "Zmieszany" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-pt-rPT/strings.xml b/samples/PluginDemo/res/values-pt-rPT/strings.xml deleted file mode 100644 index ae5eefc..0000000 --- a/samples/PluginDemo/res/values-pt-rPT/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Adicionar contacto" - "Eliminar contacto" - "Bloquear" - "Lista de contactos" - "Enviar MI" - "Informações do amigo" - "Terminar conversa" - "Trocar chats" - "Inserir ícones expressivos" - "Obter uma nova conta" - "Para sua segurança, caso perca o telefone ou este seja roubado, aceda ao Web site no computador e altere a sua palavra-passe." - "Lista de contactos - %1$s" - "Nome de utilizador:" - "Conversas (%1$d)" - "Informações de contacto" - "Disponível" - "Adicionar contacto" - "Pseudónimo da pessoa que pretende adicionar:" - "Adicionar amigo" - - "Feliz" - "Triste" - "A piscar o olho" - "Língua de fora" - "Surpreendido" - "Beijoqueiro" - "A gritar" - "Fixe" - "Interesseiro" - "Arrependido" - "Envergonhado" - "Anjo" - "Indeciso" - "Chorão" - "Boca fechada" - "Risonho" - "Confuso" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-pt/strings.xml b/samples/PluginDemo/res/values-pt/strings.xml deleted file mode 100644 index 6ff3ba5..0000000 --- a/samples/PluginDemo/res/values-pt/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Adicionar contato" - "Excluir contato" - "Bloquear" - "Lista de contatos" - "Enviar mensagem instantânea" - "Informações do amigo" - "Encerrar conversa" - "Alternar bate-papos" - "Inserir emoticons" - "Obter uma nova conta" - "Para a sua segurança, se o seu telefone for perdido ou roubado, acesse o site no seu computador e altere a sua senha." - "Lista de contatos - %1$s" - "Nome de usuário:" - "Conversas (%1$d)" - "Informações de contato" - "Disponível" - "Adicionar contato" - "Apelido da pessoa que você deseja adicionar:" - "Adicionar amigo" - - "Feliz" - "Triste" - "Piscando" - "Mostrando a língua" - "Surpreso" - "Beijando" - "Gritando" - "Tranquilo" - "Louco por dinheiro" - "Falei besteira" - "Envergonhado" - "Anjo" - "Indeciso" - "Chorando" - "Boca fechada" - "Rindo" - "Confuso" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-ru/strings.xml b/samples/PluginDemo/res/values-ru/strings.xml deleted file mode 100644 index 174f7e7..0000000 --- a/samples/PluginDemo/res/values-ru/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Добавить контакт" - "Удалить контакт" - "Заблокировать" - "Список контактов" - "Отправить сообщение чата" - "О приятеле" - "Закончить разговор" - "Сменить чат" - "Вставить значки настроений" - "Получить новый аккаунт" - "В случае потери или кражи телефона для обеспечения вашей безопасности перейдите со своего компьютера на веб-сайт и поменяйте пароль." - "Список контактов – %1$s" - "Имя пользователя:" - "Разговоров: %1$d" - "Сведения о контакте" - "На месте" - "Добавить контакт" - "Псевдоним человека, которого необходимо добавить:" - "Добавить приятеля" - - "Веселый" - "Грустный" - "Подмигивает" - "Показывает язык" - "Удивление" - "Поцелуй" - "Кричит" - "Крутой" - "Молчание – золото" - "В замешательстве" - "Смущение" - "Ангел" - "Нерешительный" - "Плачет" - "Не скажу" - "Смех" - "Озадаченный" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-sv/strings.xml b/samples/PluginDemo/res/values-sv/strings.xml deleted file mode 100644 index 845ecf2..0000000 --- a/samples/PluginDemo/res/values-sv/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Lägg till kontakt" - "Ta bort kontakt" - "Blockera" - "Kontaktlista" - "Skicka chattmeddelande" - "Kompisinfo" - "Avsluta konversation" - "Byt chatt" - "Infoga uttryckssymboler" - "Öppna ett nytt konto" - "Om din telefon blir stulen besöker du webbplatsen via datorn och ändrar lösenordet. Detta är för din egen säkerhet." - "Kontaktlista – %1$s" - "Användarnamn:" - "Konversationer (%1$d)" - "Kontaktinfo" - "Tillgänglig" - "Lägg till kontakt" - "Signatur för den du vill lägga till:" - "Lägg till kompis" - - "Glad" - "Ledsen" - "Blinkar" - "Lipar" - "Förvånad" - "Pussar" - "Skriker" - "Cool" - "Dollarmun" - "Bortgjord" - "Generad" - "Ängel" - "Tveksam" - "Gråter" - "Hemlis" - "Skrattar" - "Förvirrad" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-tr/strings.xml b/samples/PluginDemo/res/values-tr/strings.xml deleted file mode 100644 index e7813e6..0000000 --- a/samples/PluginDemo/res/values-tr/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "Kişi Ekle" - "Kişiyi Sil" - "Engelle" - "Kişi Listesi" - "IM Gönder" - "Arkadaş Bilgileri" - "İleti Dizisini Sonlandır" - "Sohbetler Arasında Geçiş Yap" - "İfade Ekle" - "Yeni hesap al" - "Telefonunuz kaybolur veya çalınırsa, güvenliğiniz için bilgisayarınızdan Web sitesine gidin ve şifrenizi değiştirin." - "Kişi Listesi - %1$s" - "Kullanıcı adı:" - "Konuşmalar (%1$d)" - "Kişi Bilgileri" - "Müsait" - "Kişi Ekle" - "Eklemek istediğiniz kişinin ekran adı:" - "Arkadaş Ekle" - - "Mutlu" - "Üzgün" - "Göz Kırpma" - "Dil çıkarmış" - "Şaşırmış" - "Öpücük" - "Bağırma" - "Sakin" - "Para ağızlı" - "Ayak ağızda" - "Utanmış" - "Melek" - "Kararsız" - "Ağlama" - "Dudakları mühürlü" - "Gülme" - "Kafası karışmış" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-zh-rCN/strings.xml b/samples/PluginDemo/res/values-zh-rCN/strings.xml deleted file mode 100644 index 839f405..0000000 --- a/samples/PluginDemo/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "添加联系人" - "删除联系人" - "阻止" - "联系人列表" - "发送即时消息" - "好友信息" - "结束会话" - "切换聊天" - "插入表情符" - "获取新帐户" - "为了您的安全,如果您的手机遗失或被窃,请通过您的计算机访问网站并更改您的密码。" - "联系人列表 - %1$s" - "用户名:" - "会话 (%1$d)" - "联系人信息" - "有空" - "添加联系人" - "您希望添加的联系人的用户名:" - "添加好友" - - "幸福" - "悲伤" - "眨眼" - "吐舌头" - "惊讶" - "亲吻" - "大喊" - "酷" - "财迷" - "说错了话" - "尴尬" - "天使" - "犹豫" - "哭泣" - "保密" - "大笑" - "困惑" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values-zh-rTW/strings.xml b/samples/PluginDemo/res/values-zh-rTW/strings.xml deleted file mode 100644 index 4412f7a..0000000 --- a/samples/PluginDemo/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - "新增聯絡人" - "刪除聯絡人" - "封鎖" - "聯絡人清單" - "傳送即時訊息" - "好友資訊" - "結束會話" - "切換即時通訊" - "插入表情符號" - "取得新帳戶" - "為了安全起見,如果電話遺失或遭竊,請使用電腦前往該網站並變更密碼。" - "聯絡人清單 - %1$s" - "使用者名稱:" - "會話 (%1$d)" - "聯絡人資訊" - "可用" - "新增聯絡人" - "要新增之聯絡人在畫面上的名稱:" - "新增好友" - - "開心" - "傷心" - "眨眼" - "吐舌頭" - "驚訝" - "紅唇" - "大喊" - "酷" - "滿嘴錢" - "說錯話" - "害羞" - "天使" - "還沒決定" - "嚎啕大哭" - "不要告訴別人" - "開懷大笑" - "疑惑" - - - ":-)" - ":-(" - ";-)" - ":-P" - "=-O" - ":-*" - ":O" - "B-)" - ":-$" - ":-!" - ":-[" - "O:-)" - ":-\\" - ":\'(" - ":-X" - ":-D" - "o_O" - - diff --git a/samples/PluginDemo/res/values/strings.xml b/samples/PluginDemo/res/values/strings.xml deleted file mode 100644 index 7afa4ab..0000000 --- a/samples/PluginDemo/res/values/strings.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - Add Contact - Delete Contact - Block - Contact List - Send IM - Buddy Info - End Conversation - Switch Chats - Insert Emoticons - Get a new account - For your security, if your phone is lost or stolen, go to the Web site on your computer and change your password. - Contact List - %1$s - Username: - Conversations (%1$d) - Contact Info - Available - Add Contact - Screen name of person you wish to add: - Add Buddy - - - Happy - Sad - Winking - Tongue sticking out - Surprised - Kissing - Yelling - Cool - Money mouth - Foot in mouth - Embarrassed - Angel - Undecided - Crying - Lips are sealed - Laughing - Confused - - - - :-) - :-( - ;-) - :-P - =-O - :-* - :O - B-) - :-$ - :-! - :-[ - O:-) - :-\\ - :\'( - :-X - :-D - o_O - - diff --git a/samples/PluginDemo/src/com/android/im/plugin/demo/DemoImPlugin.java b/samples/PluginDemo/src/com/android/im/plugin/demo/DemoImPlugin.java index eb7d0c9..5fe6cc0 100644 --- a/samples/PluginDemo/src/com/android/im/plugin/demo/DemoImPlugin.java +++ b/samples/PluginDemo/src/com/android/im/plugin/demo/DemoImPlugin.java @@ -17,7 +17,7 @@ package com.android.im.plugin.demo; import com.android.im.plugin.BrandingResourceIDs; -import com.android.im.plugin.IImPlugin; +import com.android.im.plugin.ImPlugin; import com.android.im.plugin.ImConfigNames; import com.android.im.plugin.ImpsConfigNames; @@ -33,110 +33,105 @@ import java.util.Map; * Simple example of writing a plug-in for the IM application. * */ -public class DemoImPlugin extends Service { +public class DemoImPlugin extends Service implements ImPlugin { @Override public IBinder onBind(Intent intent) { - return mBinder; + return null; } - /** - * The implementation of IImPlugin defined through AIDL. - */ - private IBinder mBinder = new IImPlugin.Stub() { - public Map getProviderConfig() { - HashMap config = new HashMap(); - // The protocol name MUST be IMPS now. - config.put(ImConfigNames.PROTOCOL_NAME, "IMPS"); - config.put(ImpsConfigNames.HOST, "http://xxx.xxxx.xxx"); - config.put(ImpsConfigNames.CLIENT_ID, "Jimmy"); - config.put(ImpsConfigNames.DATA_CHANNEL, "HTTP"); - config.put(ImpsConfigNames.DATA_ENCODING, "WBXML"); - config.put(ImpsConfigNames.CIR_CHANNEL, "STCP"); - config.put(ImpsConfigNames.CUSTOM_PRESENCE_MAPPING, - "com.android.im.plugin.demo.DemoPresenceMapping"); - return config; - } + public Map getProviderConfig() { + HashMap config = new HashMap(); + // The protocol name MUST be IMPS now. + config.put(ImConfigNames.PROTOCOL_NAME, "IMPS"); + config.put(ImpsConfigNames.HOST, "http://xxx.xxxx.xxx"); + config.put(ImpsConfigNames.CLIENT_ID, "Jimmy"); + config.put(ImpsConfigNames.DATA_CHANNEL, "HTTP"); + config.put(ImpsConfigNames.DATA_ENCODING, "WBXML"); + config.put(ImpsConfigNames.CIR_CHANNEL, "STCP"); + config.put(ImpsConfigNames.CUSTOM_PRESENCE_MAPPING, + "com.android.im.plugin.demo.DemoPresenceMapping"); + return config; + } - public Map getResourceMap() throws RemoteException { - HashMap resMapping = new HashMap(); - resMapping.put(BrandingResourceIDs.DRAWABLE_LOGO, R.drawable.im_logo); - resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_ONLINE, - android.R.drawable.presence_online); - resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_AWAY, - android.R.drawable.presence_away); - resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_BUSY, - android.R.drawable.presence_busy); - resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_INVISIBLE, - android.R.drawable.presence_invisible); - resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_OFFLINE, - android.R.drawable.presence_offline); - resMapping.put(BrandingResourceIDs.DRAWABLE_SPLASH_SCREEN, - R.drawable.im_logo_splashscr); - resMapping.put(BrandingResourceIDs.DRAWABLE_READ_CHAT, - R.drawable.chat); - resMapping.put(BrandingResourceIDs.DRAWABLE_UNREAD_CHAT, - R.drawable.chat_new); + public Map getResourceMap() { + HashMap resMapping = new HashMap(); +/* resMapping.put(BrandingResourceIDs.DRAWABLE_LOGO, R.drawable.im_logo); + resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_ONLINE, + android.R.drawable.presence_online); + resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_AWAY, + android.R.drawable.presence_away); + resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_BUSY, + android.R.drawable.presence_busy); + resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_INVISIBLE, + android.R.drawable.presence_invisible); + resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_OFFLINE, + android.R.drawable.presence_offline); + resMapping.put(BrandingResourceIDs.DRAWABLE_SPLASH_SCREEN, + R.drawable.im_logo_splashscr); + resMapping.put(BrandingResourceIDs.DRAWABLE_READ_CHAT, + R.drawable.chat); + resMapping.put(BrandingResourceIDs.DRAWABLE_UNREAD_CHAT, + R.drawable.chat_new); - resMapping.put(BrandingResourceIDs.STRING_BUDDY_LIST_TITLE, - R.string.buddy_list_title); - resMapping.put(BrandingResourceIDs.STRING_ARRAY_SMILEY_NAMES, - R.array.smiley_names); - resMapping.put(BrandingResourceIDs.STRING_ARRAY_SMILEY_TEXTS, - R.array.smiley_texts); - resMapping.put(BrandingResourceIDs.STRING_PRESENCE_AVAILABLE, - R.string.presence_available); + resMapping.put(BrandingResourceIDs.STRING_BUDDY_LIST_TITLE, + R.string.buddy_list_title); + resMapping.put(BrandingResourceIDs.STRING_ARRAY_SMILEY_NAMES, + R.array.smiley_names); + resMapping.put(BrandingResourceIDs.STRING_ARRAY_SMILEY_TEXTS, + R.array.smiley_texts); + resMapping.put(BrandingResourceIDs.STRING_PRESENCE_AVAILABLE, + R.string.presence_available); - resMapping.put(BrandingResourceIDs.STRING_LABEL_USERNAME, - R.string.label_username); - resMapping.put(BrandingResourceIDs.STRING_ONGOING_CONVERSATION, - R.string.ongoing_conversation); - resMapping.put(BrandingResourceIDs.STRING_ADD_CONTACT_TITLE, - R.string.add_contact_title); - resMapping.put(BrandingResourceIDs.STRING_LABEL_INPUT_CONTACT, - R.string.input_contact_label); - resMapping.put(BrandingResourceIDs.STRING_BUTTON_ADD_CONTACT, - R.string.invite_label); - resMapping.put(BrandingResourceIDs.STRING_CONTACT_INFO_TITLE, - R.string.contact_profile_title); + resMapping.put(BrandingResourceIDs.STRING_LABEL_USERNAME, + R.string.label_username); + resMapping.put(BrandingResourceIDs.STRING_ONGOING_CONVERSATION, + R.string.ongoing_conversation); + resMapping.put(BrandingResourceIDs.STRING_ADD_CONTACT_TITLE, + R.string.add_contact_title); + resMapping.put(BrandingResourceIDs.STRING_LABEL_INPUT_CONTACT, + R.string.input_contact_label); + resMapping.put(BrandingResourceIDs.STRING_BUTTON_ADD_CONTACT, + R.string.invite_label); + resMapping.put(BrandingResourceIDs.STRING_CONTACT_INFO_TITLE, + R.string.contact_profile_title); - resMapping.put(BrandingResourceIDs.STRING_MENU_ADD_CONTACT, - R.string.menu_add_contact); - resMapping.put(BrandingResourceIDs.STRING_MENU_BLOCK_CONTACT, - R.string.menu_block_contact); - resMapping.put(BrandingResourceIDs.STRING_MENU_CONTACT_LIST, - R.string.menu_contact_list); - resMapping.put(BrandingResourceIDs.STRING_MENU_DELETE_CONTACT, - R.string.menu_remove_contact); - resMapping.put(BrandingResourceIDs.STRING_MENU_END_CHAT, - R.string.menu_end_conversation); - resMapping.put(BrandingResourceIDs.STRING_MENU_INSERT_SMILEY, - R.string.menu_insert_smiley); - resMapping.put(BrandingResourceIDs.STRING_MENU_START_CHAT, - R.string.menu_start_chat); - resMapping.put(BrandingResourceIDs.STRING_MENU_VIEW_PROFILE, - R.string.menu_view_profile); - resMapping.put(BrandingResourceIDs.STRING_MENU_SWITCH_CHATS, - R.string.menu_switch_chats); + resMapping.put(BrandingResourceIDs.STRING_MENU_ADD_CONTACT, + R.string.menu_add_contact); + resMapping.put(BrandingResourceIDs.STRING_MENU_BLOCK_CONTACT, + R.string.menu_block_contact); + resMapping.put(BrandingResourceIDs.STRING_MENU_CONTACT_LIST, + R.string.menu_contact_list); + resMapping.put(BrandingResourceIDs.STRING_MENU_DELETE_CONTACT, + R.string.menu_remove_contact); + resMapping.put(BrandingResourceIDs.STRING_MENU_END_CHAT, + R.string.menu_end_conversation); + resMapping.put(BrandingResourceIDs.STRING_MENU_INSERT_SMILEY, + R.string.menu_insert_smiley); + resMapping.put(BrandingResourceIDs.STRING_MENU_START_CHAT, + R.string.menu_start_chat); + resMapping.put(BrandingResourceIDs.STRING_MENU_VIEW_PROFILE, + R.string.menu_view_profile); + resMapping.put(BrandingResourceIDs.STRING_MENU_SWITCH_CHATS, + R.string.menu_switch_chats); - resMapping.put(BrandingResourceIDs.STRING_TOAST_CHECK_SAVE_PASSWORD, - R.string.check_save_password); - resMapping.put(BrandingResourceIDs.STRING_LABEL_SIGN_UP, - R.string.sign_up); - return resMapping; - } + resMapping.put(BrandingResourceIDs.STRING_TOAST_CHECK_SAVE_PASSWORD, + R.string.check_save_password); + resMapping.put(BrandingResourceIDs.STRING_LABEL_SIGN_UP, + R.string.sign_up);*/ + return resMapping; + } - public int[] getSmileyIconIds() throws RemoteException { - return SMILEY_RES_IDS; - } - }; + public int[] getSmileyIconIds() { + return SMILEY_RES_IDS; + } /** * An array of the smiley icon IDs. Note that the sequence of the array MUST * match the smiley texts and smiley names defined in strings.xml. */ static final int[] SMILEY_RES_IDS = { - R.drawable.emo_im_happy, +/* R.drawable.emo_im_happy, R.drawable.emo_im_sad, R.drawable.emo_im_winking, R.drawable.emo_im_tongue_sticking_out, @@ -152,7 +147,7 @@ public class DemoImPlugin extends Service { R.drawable.emo_im_crying, R.drawable.emo_im_lips_are_sealed, R.drawable.emo_im_laughing, - R.drawable.emo_im_wtf + R.drawable.emo_im_wtf */ }; } diff --git a/samples/PluginDemo/src/com/android/im/plugin/demo/DemoPresenceMapping.java b/samples/PluginDemo/src/com/android/im/plugin/demo/DemoPresenceMapping.java index e8acb88..fc32984 100644 --- a/samples/PluginDemo/src/com/android/im/plugin/demo/DemoPresenceMapping.java +++ b/samples/PluginDemo/src/com/android/im/plugin/demo/DemoPresenceMapping.java @@ -17,7 +17,7 @@ package com.android.im.plugin.demo; import com.android.im.plugin.ImPluginConstants; -import com.android.im.plugin.IPresenceMapping; +import com.android.im.plugin.PresenceMapping; import java.util.Map; @@ -25,7 +25,7 @@ import java.util.Map; * A simple implementation of PresenceMaping for the provider. * */ -public class DemoPresenceMapping extends IPresenceMapping.Stub { +public class DemoPresenceMapping implements PresenceMapping { public int[] getSupportedPresenceStatus() { return new int[] { diff --git a/src/com/android/im/app/AccountActivity.java b/src/com/android/im/app/AccountActivity.java index fcd1ba3..d73f446 100644 --- a/src/com/android/im/app/AccountActivity.java +++ b/src/com/android/im/app/AccountActivity.java @@ -19,6 +19,7 @@ package com.android.im.app; import com.android.im.R; import com.android.im.plugin.BrandingResourceIDs; +import com.android.im.provider.Imps; import android.app.Activity; import android.app.AlertDialog; @@ -31,7 +32,6 @@ import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; -import android.provider.Im; import android.text.Editable; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -59,11 +59,11 @@ public class AccountActivity extends Activity { static final int REQUEST_SIGN_IN = RESULT_FIRST_USER + 1; private static final String[] ACCOUNT_PROJECTION = { - Im.Account._ID, - Im.Account.PROVIDER, - Im.Account.USERNAME, - Im.Account.PASSWORD, - Im.Account.KEEP_SIGNED_IN, + Imps.Account._ID, + Imps.Account.PROVIDER, + Imps.Account.USERNAME, + Imps.Account.PASSWORD, + Imps.Account.KEEP_SIGNED_IN, }; private static final int ACCOUNT_PROVIDER_COLUMN = 1; @@ -117,7 +117,7 @@ public class AccountActivity extends Activity { ContentResolver cr = getContentResolver(); Uri uri = i.getData(); - if ((uri == null) || !Im.Account.CONTENT_ITEM_TYPE.equals(cr.getType(uri))) { + if ((uri == null) || !Imps.Account.CONTENT_ITEM_TYPE.equals(cr.getType(uri))) { Log.w(ImApp.LOG_TAG, "Bad data"); return; } @@ -193,7 +193,7 @@ public class AccountActivity extends Activity { long accountId = ImApp.insertOrUpdateAccount(cr, providerId, username, rememberPass ? pass : null); - mAccountUri = ContentUris.withAppendedId(Im.Account.CONTENT_URI, accountId); + mAccountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); if (!origUserName.equals(username) && shouldShowTermOfUse(brandingRes)) { comfirmTermsOfUse(brandingRes, new DialogInterface.OnClickListener() { @@ -276,7 +276,7 @@ public class AccountActivity extends Activity { updateKeepSignedIn(false); mEditPass.setText(""); ContentValues values = new ContentValues(); - values.put(Im.Account.PASSWORD, (String) null); + values.put(Imps.Account.PASSWORD, (String) null); getContentResolver().update(mAccountUri, values, null, null); } } @@ -284,7 +284,7 @@ public class AccountActivity extends Activity { void updateKeepSignedIn(boolean keepSignIn) { ContentValues values = new ContentValues(); - values.put(Im.Account.KEEP_SIGNED_IN, keepSignIn ? 1 : 0); + values.put(Imps.Account.KEEP_SIGNED_IN, keepSignIn ? 1 : 0); getContentResolver().update(mAccountUri, values, null, null); } diff --git a/src/com/android/im/app/AddContactActivity.java b/src/com/android/im/app/AddContactActivity.java index 6e8a92d..a370d5f 100644 --- a/src/com/android/im/app/AddContactActivity.java +++ b/src/com/android/im/app/AddContactActivity.java @@ -29,7 +29,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; -import android.provider.Im; import android.provider.Contacts.ContactMethods; import android.text.Editable; import android.text.TextUtils; @@ -51,6 +50,7 @@ import com.android.im.R; import com.android.im.engine.ImErrorInfo; import com.android.im.plugin.BrandingResourceIDs; import com.android.im.plugin.ImpsConfigNames; +import com.android.im.provider.Imps; import com.android.im.service.ImServiceConstants; import java.util.List; @@ -58,8 +58,8 @@ import java.util.List; public class AddContactActivity extends Activity { private static final String[] CONTACT_LIST_PROJECTION = { - Im.ContactList._ID, - Im.ContactList.NAME, + Imps.ContactList._ID, + Imps.ContactList.NAME, }; private static final int CONTACT_LIST_NAME_COLUMN = 1; @@ -102,7 +102,7 @@ public class AddContactActivity extends Activity { SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_spinner_item, c, - new String[] {Im.ContactList.NAME}, + new String[] {Imps.ContactList.NAME}, new int[] {android.R.id.text1}); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mListSpinner.setAdapter(adapter); @@ -116,7 +116,7 @@ public class AddContactActivity extends Activity { } private Cursor queryContactLists() { - Uri uri = Im.ContactList.CONTENT_URI; + Uri uri = Imps.ContactList.CONTENT_URI; uri = ContentUris.withAppendedId(uri, mProviderId); uri = ContentUris.withAppendedId(uri, mAccountId); Cursor c = managedQuery(uri, CONTACT_LIST_PROJECTION, null, null); @@ -141,7 +141,7 @@ public class AddContactActivity extends Activity { ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, -1); mAccountId = intent.getLongExtra( ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, -1); - mDefaultDomain = Im.ProviderSettings.getStringValue(getContentResolver(), + mDefaultDomain = Imps.ProviderSettings.getStringValue(getContentResolver(), mProviderId, ImpsConfigNames.DEFAULT_DOMAIN); } diff --git a/src/com/android/im/app/BlockedContactsActivity.java b/src/com/android/im/app/BlockedContactsActivity.java index 014f596..c2aa495 100644 --- a/src/com/android/im/app/BlockedContactsActivity.java +++ b/src/com/android/im/app/BlockedContactsActivity.java @@ -28,7 +28,6 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; -import android.provider.Im; import android.util.Log; import android.view.View; import android.view.Window; @@ -40,18 +39,19 @@ import com.android.im.IContactListManager; import com.android.im.IImConnection; import com.android.im.R; import com.android.im.plugin.BrandingResourceIDs; +import com.android.im.provider.Imps; public class BlockedContactsActivity extends ListActivity { ImApp mApp; SimpleAlertHandler mHandler; private static final String[] PROJECTION = { - Im.BlockedList._ID, - Im.BlockedList.ACCOUNT, - Im.BlockedList.PROVIDER, - Im.BlockedList.NICKNAME, - Im.BlockedList.USERNAME, - Im.BlockedList.AVATAR_DATA, + Imps.BlockedList._ID, + Imps.BlockedList.ACCOUNT, + Imps.BlockedList.PROVIDER, + Imps.BlockedList.NICKNAME, + Imps.BlockedList.USERNAME, + Imps.BlockedList.AVATAR_DATA, }; static final int ACCOUNT_COLUMN = 1; @@ -99,7 +99,7 @@ public class BlockedContactsActivity extends ListActivity { } long accountId = ContentUris.parseId(uri); - Uri accountUri = ContentUris.withAppendedId(Im.Account.CONTENT_URI, accountId); + Uri accountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); Cursor accountCursor = getContentResolver().query(accountUri, null, null, null, null); if (accountCursor == null) { warning("Bad account"); @@ -112,9 +112,9 @@ public class BlockedContactsActivity extends ListActivity { } long providerId = accountCursor.getLong( - accountCursor.getColumnIndexOrThrow(Im.Account.PROVIDER)); + accountCursor.getColumnIndexOrThrow(Imps.Account.PROVIDER)); String username = accountCursor.getString( - accountCursor.getColumnIndexOrThrow(Im.Account.USERNAME)); + accountCursor.getColumnIndexOrThrow(Imps.Account.USERNAME)); BrandingResources brandingRes = mApp.getBrandingResource(providerId); getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, @@ -123,7 +123,7 @@ public class BlockedContactsActivity extends ListActivity { setTitle(getResources().getString(R.string.blocked_list_title, username)); accountCursor.close(); - Cursor c = managedQuery(uri, PROJECTION, null, Im.BlockedList.DEFAULT_SORT_ORDER); + Cursor c = managedQuery(uri, PROJECTION, null, Imps.BlockedList.DEFAULT_SORT_ORDER); if (c == null) { warning("Database error when query " + uri); return false; diff --git a/src/com/android/im/app/BrandingResources.java b/src/com/android/im/app/BrandingResources.java index 97dd698..6708b9e 100644 --- a/src/com/android/im/app/BrandingResources.java +++ b/src/com/android/im/app/BrandingResources.java @@ -17,7 +17,7 @@ package com.android.im.app; -import com.android.im.plugin.IImPlugin; +import com.android.im.plugin.ImPlugin; import com.android.im.plugin.ImPluginInfo; import dalvik.system.PathClassLoader; @@ -68,12 +68,11 @@ public class BrandingResources { // Load the plug-in directly from the apk instead of binding the service // and calling through the IPC binder API. It's more effective in this way // and we can avoid the async behaviors of binding service. - PathClassLoader classLoader = new PathClassLoader(pluginInfo.mSrcPath, - context.getClassLoader()); + ClassLoader classLoader = context.getClassLoader(); try { Class cls = classLoader.loadClass(pluginInfo.mClassName); Method m = cls.getMethod("onBind", Intent.class); - IImPlugin plugin = (IImPlugin)m.invoke(cls.newInstance(), new Object[]{null}); + ImPlugin plugin = (ImPlugin)m.invoke(cls.newInstance(), new Object[]{null}); mResMapping = plugin.getResourceMap(); mSmileyIcons = plugin.getSmileyIconIds(); } catch (ClassNotFoundException e) { @@ -90,8 +89,6 @@ public class BrandingResources { Log.e(TAG, "Failed load the plugin resource map", e); } catch (InvocationTargetException e) { Log.e(TAG, "Failed load the plugin resource map", e); - } catch (RemoteException e) { - Log.e(TAG, "Failed load the plugin resource map", e); } } @@ -104,8 +101,14 @@ public class BrandingResources { */ public BrandingResources(Context context, Map resMapping, BrandingResources defaultRes) { - mPackageRes = context.getResources(); + this(context.getResources(), resMapping, null, defaultRes); + } + + public BrandingResources(Resources packageRes, Map resMapping, + int[] smileyIcons, BrandingResources defaultRes) { + mPackageRes = packageRes; mResMapping = resMapping; + mSmileyIcons = smileyIcons; mDefaultRes = defaultRes; } diff --git a/src/com/android/im/app/ChatBackgroundMaker.java b/src/com/android/im/app/ChatBackgroundMaker.java index fe340e5..12a9a85 100644 --- a/src/com/android/im/app/ChatBackgroundMaker.java +++ b/src/com/android/im/app/ChatBackgroundMaker.java @@ -18,12 +18,12 @@ package com.android.im.app; import com.android.im.R; +import com.android.im.provider.Imps; import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.provider.Im; import android.view.View; public class ChatBackgroundMaker { @@ -43,13 +43,13 @@ public class ChatBackgroundMaker { View msgText = view.findViewById(R.id.message); switch (type) { - case Im.MessageType.INCOMING: + case Imps.MessageType.INCOMING: // TODO: set color according different contact msgText.setBackgroundDrawable(mIncomingBg); break; - case Im.MessageType.OUTGOING: - case Im.MessageType.POSTPONED: + case Imps.MessageType.OUTGOING: + case Imps.MessageType.POSTPONED: msgText.setBackgroundDrawable(null); msgText.setPadding(mPadding.left, mPadding.top, mPadding.right, mPadding.bottom); diff --git a/src/com/android/im/app/ChatSwitcher.java b/src/com/android/im/app/ChatSwitcher.java index 04d35e6..731128a 100644 --- a/src/com/android/im/app/ChatSwitcher.java +++ b/src/com/android/im/app/ChatSwitcher.java @@ -21,6 +21,7 @@ import java.util.List; import com.android.im.R; import com.android.im.plugin.BrandingResourceIDs; +import com.android.im.provider.Imps; import android.app.Activity; import android.app.Dialog; @@ -33,7 +34,6 @@ import android.content.res.Configuration; import android.database.Cursor; import android.database.DataSetObserver; import android.graphics.drawable.Drawable; -import android.provider.Im; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; @@ -51,7 +51,7 @@ public class ChatSwitcher { private static final boolean LOCAL_DEBUG = true; private static final String[] PROVIDER_CATEGORY_PROJECTION = new String[] { - Im.Provider.CATEGORY + Imps.Provider.CATEGORY }; private static final int PROVIDER_CATEGORY_COLUMN = 0; @@ -130,7 +130,7 @@ public class ChatSwitcher { mQueryHandler.startQuery( sQueryToken, runnable, - Im.Contacts.CONTENT_URI_CHAT_CONTACTS, + Imps.Contacts.CONTENT_URI_CHAT_CONTACTS, null, /*projection*/ mQuerySelection, mQuerySelectionArgs, @@ -452,17 +452,17 @@ public class ChatSwitcher { protected void onQueryComplete(int token, Object cookie, Cursor cursor) { if (cursor != null) { - mContactIdColumn = cursor.getColumnIndexOrThrow(Im.Contacts._ID); - mProviderIdColumn = cursor.getColumnIndexOrThrow(Im.Contacts.PROVIDER); - mAccountIdColumn = cursor.getColumnIndexOrThrow(Im.Contacts.ACCOUNT); - mUsernameColumn = cursor.getColumnIndexOrThrow(Im.Contacts.USERNAME); - mNicknameColumn = cursor.getColumnIndexOrThrow(Im.Contacts.NICKNAME); - mPresenceStatusColumn = cursor.getColumnIndexOrThrow(Im.Contacts.PRESENCE_STATUS); - mLastUnreadMessageColumn = cursor.getColumnIndexOrThrow(Im.Chats.LAST_UNREAD_MESSAGE); - mAvatarDataColumn = cursor.getColumnIndexOrThrow(Im.Contacts.AVATAR_DATA); - mShortcutColumn = cursor.getColumnIndexOrThrow(Im.Chats.SHORTCUT); - mLastChatColumn = cursor.getColumnIndexOrThrow(Im.Chats.LAST_MESSAGE_DATE); - mGroupChatColumn = cursor.getColumnIndexOrThrow(Im.Chats.GROUP_CHAT); + mContactIdColumn = cursor.getColumnIndexOrThrow(Imps.Contacts._ID); + mProviderIdColumn = cursor.getColumnIndexOrThrow(Imps.Contacts.PROVIDER); + mAccountIdColumn = cursor.getColumnIndexOrThrow(Imps.Contacts.ACCOUNT); + mUsernameColumn = cursor.getColumnIndexOrThrow(Imps.Contacts.USERNAME); + mNicknameColumn = cursor.getColumnIndexOrThrow(Imps.Contacts.NICKNAME); + mPresenceStatusColumn = cursor.getColumnIndexOrThrow(Imps.Contacts.PRESENCE_STATUS); + mLastUnreadMessageColumn = cursor.getColumnIndexOrThrow(Imps.Chats.LAST_UNREAD_MESSAGE); + mAvatarDataColumn = cursor.getColumnIndexOrThrow(Imps.Contacts.AVATAR_DATA); + mShortcutColumn = cursor.getColumnIndexOrThrow(Imps.Chats.SHORTCUT); + mLastChatColumn = cursor.getColumnIndexOrThrow(Imps.Chats.LAST_MESSAGE_DATE); + mGroupChatColumn = cursor.getColumnIndexOrThrow(Imps.Chats.GROUP_CHAT); } mOkToShowEmptyView = true; @@ -496,7 +496,7 @@ public class ChatSwitcher { buf.append(" OR "); } - buf.append(Im.Contacts.PROVIDER).append("=?"); + buf.append(Imps.Contacts.PROVIDER).append("=?"); mQuerySelectionArgs[i] = String.valueOf(providerDef.mId); i++; } @@ -510,7 +510,7 @@ public class ChatSwitcher { private static String findCategory(ContentResolver resolver, long providerId) { // find the provider category for this chat Cursor providerCursor = resolver.query( - Im.Provider.CONTENT_URI, + Imps.Provider.CONTENT_URI, PROVIDER_CATEGORY_PROJECTION, "_id = " + providerId, null /* selection args */, @@ -532,7 +532,7 @@ public class ChatSwitcher { public static Intent makeChatIntent(ContentResolver resolver, long provider, long account, String contact, long contactId, int groupChat) { Intent i = new Intent(Intent.ACTION_VIEW, - ContentUris.withAppendedId(Im.Chats.CONTENT_URI, contactId)); + ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, contactId)); i.addCategory(findCategory(resolver, provider)); i.putExtra("from", contact); i.putExtra("providerId", provider); diff --git a/src/com/android/im/app/ChatView.java b/src/com/android/im/app/ChatView.java index 665c7a5..ebc1038 100644 --- a/src/com/android/im/app/ChatView.java +++ b/src/com/android/im/app/ChatView.java @@ -42,7 +42,6 @@ import android.os.Bundle; import android.os.Message; import android.os.RemoteException; import android.provider.Browser; -import android.provider.Im; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -84,18 +83,19 @@ import com.android.im.engine.Contact; import com.android.im.engine.ImConnection; import com.android.im.engine.ImErrorInfo; import com.android.im.plugin.BrandingResourceIDs; +import com.android.im.provider.Imps; public class ChatView extends LinearLayout { // This projection and index are set for the query of active chats static final String[] CHAT_PROJECTION = { - Im.Contacts._ID, - Im.Contacts.ACCOUNT, - Im.Contacts.PROVIDER, - Im.Contacts.USERNAME, - Im.Contacts.NICKNAME, - Im.Contacts.TYPE, - Im.Presence.PRESENCE_STATUS, - Im.Chats.LAST_UNREAD_MESSAGE, + Imps.Contacts._ID, + Imps.Contacts.ACCOUNT, + Imps.Contacts.PROVIDER, + Imps.Contacts.USERNAME, + Imps.Contacts.NICKNAME, + Imps.Contacts.TYPE, + Imps.Presence.PRESENCE_STATUS, + Imps.Chats.LAST_UNREAD_MESSAGE, }; static final int CONTACT_ID_COLUMN = 0; static final int ACCOUNT_COLUMN = 1; @@ -107,9 +107,9 @@ public class ChatView extends LinearLayout { static final int LAST_UNREAD_MESSAGE_COLUMN = 7; static final String[] INVITATION_PROJECT = { - Im.Invitation._ID, - Im.Invitation.PROVIDER, - Im.Invitation.SENDER, + Imps.Invitation._ID, + Imps.Invitation.PROVIDER, + Imps.Invitation.SENDER, }; static final int INVITATION_ID_COLUMN = 0; static final int INVITATION_PROVIDER_COLUMN = 1; @@ -480,9 +480,9 @@ public class ChatView extends LinearLayout { } private void setTitle() { - if (mType == Im.Contacts.TYPE_GROUP) { - final String[] projection = {Im.GroupMembers.NICKNAME}; - Uri memberUri = ContentUris.withAppendedId(Im.GroupMembers.CONTENT_URI, mChatId); + if (mType == Imps.Contacts.TYPE_GROUP) { + final String[] projection = {Imps.GroupMembers.NICKNAME}; + Uri memberUri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, mChatId); ContentResolver cr = mScreen.getContentResolver(); Cursor c = cr.query(memberUri, projection, null, null, null); StringBuilder buf = new StringBuilder(); @@ -502,7 +502,7 @@ public class ChatView extends LinearLayout { } private void setStatusIcon() { - if (mType == Im.Contacts.TYPE_GROUP) { + if (mType == Imps.Contacts.TYPE_GROUP) { // hide the status icon for group chat. mStatusIcon.setVisibility(GONE); } else { @@ -517,7 +517,7 @@ public class ChatView extends LinearLayout { if (mCursor != null) { mCursor.deactivate(); } - Uri contactUri = ContentUris.withAppendedId(Im.Contacts.CONTENT_URI, chatId); + Uri contactUri = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, chatId); mCursor = mScreen.managedQuery(contactUri, CHAT_PROJECTION, null, null); if (mCursor == null || !mCursor.moveToFirst()) { if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){ @@ -533,7 +533,7 @@ public class ChatView extends LinearLayout { } public void bindInvitation(long invitationId) { - Uri uri = ContentUris.withAppendedId(Im.Invitation.CONTENT_URI, invitationId); + Uri uri = ContentUris.withAppendedId(Imps.Invitation.CONTENT_URI, invitationId); ContentResolver cr = mScreen.getContentResolver(); Cursor cursor = cr.query(uri, INVITATION_PROJECT, null, null, null); if (cursor == null || !cursor.moveToFirst()) { @@ -656,12 +656,7 @@ public class ChatView extends LinearLayout { mQueryHandler.cancelOperation(QUERY_TOKEN); } - Uri uri; - if (Im.Contacts.TYPE_GROUP == mType) { - uri = ContentUris.withAppendedId(Im.GroupMessages.CONTENT_URI_GROUP_MESSAGES_BY, mChatId); - } else { - uri = Im.Messages.getContentUriByContact(mProviderId, mAccountId, mUserName); - } + Uri uri = Imps.Messages.getContentUriByThreadId(mChatId); if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){ log("queryCursor: uri=" + uri); @@ -728,7 +723,7 @@ public class ChatView extends LinearLayout { } else { // the conversation is already closed, clear data in database ContentResolver cr = mContext.getContentResolver(); - cr.delete(ContentUris.withAppendedId(Im.Chats.CONTENT_URI, mChatId), + cr.delete(ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, mChatId), null, null); } mScreen.finish(); @@ -745,7 +740,7 @@ public class ChatView extends LinearLayout { } public void viewProfile() { - Uri data = ContentUris.withAppendedId(Im.Contacts.CONTENT_URI, mChatId); + Uri data = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, mChatId); Intent intent = new Intent(Intent.ACTION_VIEW, data); mScreen.startActivity(intent); } @@ -833,7 +828,7 @@ public class ChatView extends LinearLayout { } boolean isGroupChat() { - return Im.Contacts.TYPE_GROUP == mType; + return Imps.Contacts.TYPE_GROUP == mType; } void sendMessage() { @@ -943,10 +938,10 @@ public class ChatView extends LinearLayout { } if (isConnected) { - if (mType == Im.Contacts.TYPE_TEMPORARY) { + if (mType == Imps.Contacts.TYPE_TEMPORARY) { visibility = View.VISIBLE; message = mContext.getString(R.string.contact_not_in_list_warning, mNickName); - } else if (mPresenceStatus == Im.Presence.OFFLINE) { + } else if (mPresenceStatus == Imps.Presence.OFFLINE) { visibility = View.VISIBLE; message = mContext.getString(R.string.contact_offline_warning, mNickName); } @@ -1054,7 +1049,7 @@ public class ChatView extends LinearLayout { for (int i = 0 ; i < len ; i++) { mColumnNames[i] = columnNames[i]; - if (mColumnNames[i].equals(Im.BaseMessageColumns.DATE)) { + if (mColumnNames[i].equals(Imps.Messages.DATE)) { mDateColumn = i; } } @@ -1418,7 +1413,7 @@ public class ChatView extends LinearLayout { private int mScrollState; private boolean mNeedRequeryCursor; - private int mContactColumn; + private int mNicknameColumn; private int mBodyColumn; private int mDateColumn; private int mTypeColumn; @@ -1438,11 +1433,11 @@ public class ChatView extends LinearLayout { } private void resolveColumnIndex(Cursor c) { - mContactColumn = c.getColumnIndexOrThrow(Im.BaseMessageColumns.CONTACT); - mBodyColumn = c.getColumnIndexOrThrow(Im.BaseMessageColumns.BODY); - mDateColumn = c.getColumnIndexOrThrow(Im.BaseMessageColumns.DATE); - mTypeColumn = c.getColumnIndexOrThrow(Im.BaseMessageColumns.TYPE); - mErrCodeColumn = c.getColumnIndexOrThrow(Im.BaseMessageColumns.ERROR_CODE); + mNicknameColumn = c.getColumnIndexOrThrow(Imps.Messages.NICKNAME); + mBodyColumn = c.getColumnIndexOrThrow(Imps.Messages.BODY); + mDateColumn = c.getColumnIndexOrThrow(Imps.Messages.DATE); + mTypeColumn = c.getColumnIndexOrThrow(Imps.Messages.TYPE); + mErrCodeColumn = c.getColumnIndexOrThrow(Imps.Messages.ERROR_CODE); mDeltaColumn = c.getColumnIndexOrThrow(DeltaCursor.DELTA_COLUMN_NAME); } @@ -1464,19 +1459,19 @@ public class ChatView extends LinearLayout { MessageView chatMsgView = (MessageView) view; int type = cursor.getInt(mTypeColumn); - String contact = isGroupChat() ? cursor.getString(mContactColumn) : mNickName; + String contact = isGroupChat() ? cursor.getString(mNicknameColumn) : mNickName; String body = cursor.getString(mBodyColumn); long delta = cursor.getLong(mDeltaColumn); boolean showTimeStamp = (delta > SHOW_TIME_STAMP_INTERVAL); Date date = showTimeStamp ? new Date(cursor.getLong(mDateColumn)) : null; switch (type) { - case Im.MessageType.INCOMING: + case Imps.MessageType.INCOMING: chatMsgView.bindIncomingMessage(contact, body, date, mMarkup, isScrolling()); break; - case Im.MessageType.OUTGOING: - case Im.MessageType.POSTPONED: + case Imps.MessageType.OUTGOING: + case Imps.MessageType.POSTPONED: int errCode = cursor.getInt(mErrCodeColumn); if (errCode != 0) { chatMsgView.bindErrorMessage(errCode); diff --git a/src/com/android/im/app/ChooseAccountActivity.java b/src/com/android/im/app/ChooseAccountActivity.java index b18b0b2..6b0b57b 100644 --- a/src/com/android/im/app/ChooseAccountActivity.java +++ b/src/com/android/im/app/ChooseAccountActivity.java @@ -17,10 +17,11 @@ package com.android.im.app; +import com.android.im.provider.Imps; + import android.app.Activity; import android.os.Bundle; import android.content.Intent; -import android.provider.Im; public class ChooseAccountActivity extends Activity { @Override @@ -28,7 +29,7 @@ public class ChooseAccountActivity extends Activity { super.onCreate(icicle); Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setType(Im.Provider.CONTENT_TYPE); + intent.setType(Imps.Provider.CONTENT_TYPE); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); diff --git a/src/com/android/im/app/ContactListActivity.java b/src/com/android/im/app/ContactListActivity.java index 544d254..a907e25 100644 --- a/src/com/android/im/app/ContactListActivity.java +++ b/src/com/android/im/app/ContactListActivity.java @@ -19,6 +19,7 @@ package com.android.im.app; import com.android.im.IImConnection; import com.android.im.R; import com.android.im.plugin.BrandingResourceIDs; +import com.android.im.provider.Imps; import com.android.im.service.ImServiceConstants; import android.app.Activity; @@ -31,7 +32,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.RemoteException; -import android.provider.Im; import android.util.Log; import android.view.ContextMenu; import android.view.KeyEvent; @@ -72,7 +72,7 @@ public class ContactListActivity extends Activity implements View.OnCreateContex boolean mIsFiltering; - Im.ProviderSettings.QueryMap mSettingMap; + Imps.ProviderSettings.QueryMap mSettingMap; boolean mDestroyed; @Override @@ -96,7 +96,7 @@ public class ContactListActivity extends Activity implements View.OnCreateContex mApp = ImApp.getApplication(this); ContentResolver cr = getContentResolver(); - Cursor c = cr.query(ContentUris.withAppendedId(Im.Account.CONTENT_URI, mAccountId), + Cursor c = cr.query(ContentUris.withAppendedId(Imps.Account.CONTENT_URI, mAccountId), null, null, null, null); if (c == null) { finish(); @@ -108,9 +108,9 @@ public class ContactListActivity extends Activity implements View.OnCreateContex return; } - mProviderId = c.getLong(c.getColumnIndexOrThrow(Im.Account.PROVIDER)); + mProviderId = c.getLong(c.getColumnIndexOrThrow(Imps.Account.PROVIDER)); mHandler = new MyHandler(this); - String username = c.getString(c.getColumnIndexOrThrow(Im.Account.USERNAME)); + String username = c.getString(c.getColumnIndexOrThrow(Imps.Account.USERNAME)); BrandingResources brandingRes = mApp.getBrandingResource(mProviderId); setTitle(brandingRes.getString( @@ -118,7 +118,7 @@ public class ContactListActivity extends Activity implements View.OnCreateContex getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, brandingRes.getDrawable(BrandingResourceIDs.DRAWABLE_LOGO)); - mSettingMap = new Im.ProviderSettings.QueryMap( + mSettingMap = new Imps.ProviderSettings.QueryMap( getContentResolver(), mProviderId, true, null); mApp.callWhenServiceConnected(mHandler, new Runnable(){ @@ -177,7 +177,7 @@ public class ContactListActivity extends Activity implements View.OnCreateContex return true; case R.id.menu_blocked_contacts: - Uri.Builder builder = Im.BlockedList.CONTENT_URI.buildUpon(); + Uri.Builder builder = Imps.BlockedList.CONTENT_URI.buildUpon(); ContentUris.appendId(builder, mProviderId); ContentUris.appendId(builder, mAccountId); startActivity(new Intent(Intent.ACTION_VIEW, builder.build())); @@ -185,7 +185,7 @@ public class ContactListActivity extends Activity implements View.OnCreateContex case R.id.menu_view_accounts: Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setType(Im.Provider.CONTENT_TYPE); + intent.setType(Imps.Provider.CONTENT_TYPE); startActivity(intent); finish(); return true; @@ -276,8 +276,8 @@ public class ContactListActivity extends Activity implements View.OnCreateContex R.layout.contact_list_filter_view, null); mFilterView.getListView().setOnCreateContextMenuListener(this); } - Uri uri = mSettingMap.getHideOfflineContacts() ? Im.Contacts.CONTENT_URI_ONLINE_CONTACTS_BY - : Im.Contacts.CONTENT_URI_CONTACTS_BY; + Uri uri = mSettingMap.getHideOfflineContacts() ? Imps.Contacts.CONTENT_URI_ONLINE_CONTACTS_BY + : Imps.Contacts.CONTENT_URI_CONTACTS_BY; uri = ContentUris.withAppendedId(uri, mProviderId); uri = ContentUris.withAppendedId(uri, mAccountId); mFilterView.doFilter(uri, null); @@ -346,12 +346,12 @@ public class ContactListActivity extends Activity implements View.OnCreateContex if (contactCursor != null) { //XXX HACK: Yahoo! doesn't allow to block a friend. We can only block a temporary contact. ProviderDef provider = mApp.getProvider(mProviderId); - if (Im.ProviderNames.YAHOO.equals(provider.mName)) { - int type = contactCursor.getInt(contactCursor.getColumnIndexOrThrow(Im.Contacts.TYPE)); - allowBlock = (type == Im.Contacts.TYPE_TEMPORARY); + if (Imps.ProviderNames.YAHOO.equals(provider.mName)) { + int type = contactCursor.getInt(contactCursor.getColumnIndexOrThrow(Imps.Contacts.TYPE)); + allowBlock = (type == Imps.Contacts.TYPE_TEMPORARY); } - int nickNameIndex = contactCursor.getColumnIndexOrThrow(Im.Contacts.NICKNAME); + int nickNameIndex = contactCursor.getColumnIndexOrThrow(Imps.Contacts.NICKNAME); menu.setHeaderTitle(contactCursor.getString(nickNameIndex)); } @@ -402,11 +402,11 @@ public class ContactListActivity extends Activity implements View.OnCreateContex ContentResolver cr = getContentResolver(); ContentValues values = new ContentValues(3); - values.put(Im.AccountStatus.ACCOUNT, mAccountId); - values.put(Im.AccountStatus.PRESENCE_STATUS, Im.Presence.OFFLINE); - values.put(Im.AccountStatus.CONNECTION_STATUS, Im.ConnectionStatus.OFFLINE); + values.put(Imps.AccountStatus.ACCOUNT, mAccountId); + values.put(Imps.AccountStatus.PRESENCE_STATUS, Imps.Presence.OFFLINE); + values.put(Imps.AccountStatus.CONNECTION_STATUS, Imps.ConnectionStatus.OFFLINE); // insert on the "account_status" uri actually replaces the existing value - cr.insert(Im.AccountStatus.CONTENT_URI, values); + cr.insert(Imps.AccountStatus.CONTENT_URI, values); } final class ContextMenuHandler implements MenuItem.OnMenuItemClickListener { diff --git a/src/com/android/im/app/ContactListFilterView.java b/src/com/android/im/app/ContactListFilterView.java index 5607cde..76b410c 100644 --- a/src/com/android/im/app/ContactListFilterView.java +++ b/src/com/android/im/app/ContactListFilterView.java @@ -21,7 +21,6 @@ import android.content.Context; import android.database.Cursor; import android.database.DatabaseUtils; import android.net.Uri; -import android.provider.Im; import android.util.AttributeSet; import android.view.View; import android.widget.AdapterView; @@ -32,6 +31,7 @@ import android.widget.ResourceCursorAdapter; import android.widget.AdapterView.OnItemClickListener; import com.android.im.R; +import com.android.im.provider.Imps; public class ContactListFilterView extends LinearLayout { @@ -95,18 +95,18 @@ public class ContactListFilterView extends LinearLayout { StringBuilder buf = new StringBuilder(); // exclude chatting contact - buf.append(Im.Chats.LAST_MESSAGE_DATE); + buf.append(Imps.Chats.LAST_MESSAGE_DATE); buf.append(" IS NULL"); if (constraint != null) { buf.append(" AND "); - buf.append(Im.Contacts.NICKNAME); + buf.append(Imps.Contacts.NICKNAME); buf.append(" LIKE "); DatabaseUtils.appendValueToSql(buf, "%" + constraint + "%"); } return mContext.getContentResolver().query(mUri, ContactView.CONTACT_PROJECTION, - buf == null ? null : buf.toString(), null, Im.Contacts.DEFAULT_SORT_ORDER); + buf == null ? null : buf.toString(), null, Imps.Contacts.DEFAULT_SORT_ORDER); } private class ContactAdapter extends ResourceCursorAdapter { diff --git a/src/com/android/im/app/ContactListTreeAdapter.java b/src/com/android/im/app/ContactListTreeAdapter.java index be8b2d9..8b7abbb 100644 --- a/src/com/android/im/app/ContactListTreeAdapter.java +++ b/src/com/android/im/app/ContactListTreeAdapter.java @@ -29,7 +29,6 @@ import android.database.Cursor; import android.database.DataSetObserver; import android.net.Uri; import android.os.RemoteException; -import android.provider.Im; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -43,6 +42,7 @@ import android.widget.AbsListView.OnScrollListener; import com.android.im.IImConnection; import com.android.im.R; import com.android.im.plugin.BrandingResourceIDs; +import com.android.im.provider.Imps; import java.util.ArrayList; import java.util.Observable; @@ -52,8 +52,8 @@ public class ContactListTreeAdapter extends BaseExpandableListAdapter implements AbsListView.OnScrollListener{ private static final String[] CONTACT_LIST_PROJECTION = { - Im.ContactList._ID, - Im.ContactList.NAME, + Imps.ContactList._ID, + Imps.ContactList.NAME, }; private static final int COLUMN_CONTACT_LIST_ID = 0; @@ -80,22 +80,22 @@ public class ContactListTreeAdapter extends BaseExpandableListAdapter private static final int TOKEN_SUBSCRITPTION = -3; private static final String NON_CHAT_AND_BLOCKED_CONTACTS = "(" - + Im.Contacts.LAST_MESSAGE_DATE + " IS NULL) AND (" - + Im.Contacts.TYPE + "!=" + Im.Contacts.TYPE_BLOCKED + ")"; + + Imps.Contacts.LAST_MESSAGE_DATE + " IS NULL) AND (" + + Imps.Contacts.TYPE + "!=" + Imps.Contacts.TYPE_BLOCKED + ")"; - private static final String CONTACTS_SELECTION = Im.Contacts.CONTACTLIST + private static final String CONTACTS_SELECTION = Imps.Contacts.CONTACTLIST + "=? AND " + NON_CHAT_AND_BLOCKED_CONTACTS; private static final String ONLINE_CONTACT_SELECTION = CONTACTS_SELECTION - + " AND "+ Im.Contacts.PRESENCE_STATUS + " != " + Im.Presence.OFFLINE; + + " AND "+ Imps.Contacts.PRESENCE_STATUS + " != " + Imps.Presence.OFFLINE; static final void log(String msg) { Log.d(ImApp.LOG_TAG, "" + msg); } static final String[] CONTACT_COUNT_PROJECTION = { - Im.Contacts.CONTACTLIST, - Im.Contacts._COUNT, + Imps.Contacts.CONTACTLIST, + Imps.Contacts._COUNT, }; ContentQueryMap mOnlineContactsCountMap; @@ -211,12 +211,12 @@ public class ContactListTreeAdapter extends BaseExpandableListAdapter log("startQueryContactLists()"); } - Uri uri = Im.ContactList.CONTENT_URI; + Uri uri = Imps.ContactList.CONTENT_URI; uri = ContentUris.withAppendedId(uri, mProviderId); uri = ContentUris.withAppendedId(uri, mAccountId); mQueryHandler.startQuery(TOKEN_CONTACT_LISTS, null, uri, CONTACT_LIST_PROJECTION, - null, null, Im.ContactList.DEFAULT_SORT_ORDER); + null, null, Imps.ContactList.DEFAULT_SORT_ORDER); } void startQueryOngoingConversations() { @@ -224,12 +224,12 @@ public class ContactListTreeAdapter extends BaseExpandableListAdapter log("startQueryOngoingConversations()"); } - Uri uri = Im.Contacts.CONTENT_URI_CHAT_CONTACTS_BY; + Uri uri = Imps.Contacts.CONTENT_URI_CHAT_CONTACTS_BY; uri = ContentUris.withAppendedId(uri, mProviderId); uri = ContentUris.withAppendedId(uri, mAccountId); mQueryHandler.startQuery(TOKEN_ONGOING_CONVERSATION, null, uri, - ContactView.CONTACT_PROJECTION, null, null, Im.Contacts.DEFAULT_SORT_ORDER); + ContactView.CONTACT_PROJECTION, null, null, Imps.Contacts.DEFAULT_SORT_ORDER); } void startQuerySubscriptions() { @@ -237,16 +237,16 @@ public class ContactListTreeAdapter extends BaseExpandableListAdapter log("startQuerySubscriptions()"); } - Uri uri = Im.Contacts.CONTENT_URI_CONTACTS_BY; + Uri uri = Imps.Contacts.CONTENT_URI_CONTACTS_BY; uri = ContentUris.withAppendedId(uri, mProviderId); uri = ContentUris.withAppendedId(uri, mAccountId); mQueryHandler.startQuery(TOKEN_SUBSCRITPTION, null, uri, ContactView.CONTACT_PROJECTION, String.format("%s=%d AND %s=%d", - Im.Contacts.SUBSCRIPTION_STATUS, Im.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING, - Im.Contacts.SUBSCRIPTION_TYPE, Im.Contacts.SUBSCRIPTION_TYPE_FROM), - null,Im.Contacts.DEFAULT_SORT_ORDER); + Imps.Contacts.SUBSCRIPTION_STATUS, Imps.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING, + Imps.Contacts.SUBSCRIPTION_TYPE, Imps.Contacts.SUBSCRIPTION_TYPE_FROM), + null,Imps.Contacts.DEFAULT_SORT_ORDER); } void startQueryContacts(long listId) { @@ -257,8 +257,8 @@ public class ContactListTreeAdapter extends BaseExpandableListAdapter String selection = mHideOfflineContacts ? ONLINE_CONTACT_SELECTION : CONTACTS_SELECTION; String[] args = { Long.toString(listId) }; int token = (int)listId; - mQueryHandler.startQuery(token, null, Im.Contacts.CONTENT_URI, - ContactView.CONTACT_PROJECTION, selection, args, Im.Contacts.DEFAULT_SORT_ORDER); + mQueryHandler.startQuery(token, null, Imps.Contacts.CONTENT_URI, + ContactView.CONTACT_PROJECTION, selection, args, Imps.Contacts.DEFAULT_SORT_ORDER); } public Object getChild(int groupPosition, int childPosition) { @@ -640,20 +640,20 @@ public class ContactListTreeAdapter extends BaseExpandableListAdapter private int getOnlineChildCount(Cursor groupCursor) { long listId = groupCursor.getLong(COLUMN_CONTACT_LIST_ID); if (mOnlineContactsCountMap == null) { - String where = Im.Contacts.ACCOUNT + "=" + mAccountId; + String where = Imps.Contacts.ACCOUNT + "=" + mAccountId; ContentResolver cr = mActivity.getContentResolver(); - Cursor c = cr.query(Im.Contacts.CONTENT_URI_ONLINE_COUNT, + Cursor c = cr.query(Imps.Contacts.CONTENT_URI_ONLINE_COUNT, CONTACT_COUNT_PROJECTION, where, null, null); mOnlineContactsCountMap = new ContentQueryMap(c, - Im.Contacts.CONTACTLIST, true, mHandler); + Imps.Contacts.CONTACTLIST, true, mHandler); mOnlineContactsCountMap.addObserver(new Observer(){ public void update(Observable observable, Object data) { notifyDataSetChanged(); }}); } ContentValues value = mOnlineContactsCountMap.getValues(String.valueOf(listId)); - return value == null ? 0 : value.getAsInteger(Im.Contacts._COUNT); + return value == null ? 0 : value.getAsInteger(Imps.Contacts._COUNT); } @Override diff --git a/src/com/android/im/app/ContactListView.java b/src/com/android/im/app/ContactListView.java index 5c31683..12ff110 100644 --- a/src/com/android/im/app/ContactListView.java +++ b/src/com/android/im/app/ContactListView.java @@ -27,6 +27,7 @@ import com.android.im.app.adapter.ContactListListenerAdapter; import com.android.im.engine.Contact; import com.android.im.engine.ContactListManager; import com.android.im.engine.ImErrorInfo; +import com.android.im.provider.Imps; import com.android.im.service.ImServiceConstants; import android.app.Activity; @@ -42,7 +43,6 @@ import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; -import android.provider.Im; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -176,8 +176,8 @@ public class ContactListView extends LinearLayout { void startChat(Cursor c) { if (c != null) { - long id = c.getLong(c.getColumnIndexOrThrow(Im.Contacts._ID)); - String username = c.getString(c.getColumnIndexOrThrow(Im.Contacts.USERNAME)); + long id = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts._ID)); + String username = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); try { IChatSessionManager manager = mConn.getChatSessionManager(); IChatSession session = manager.getChatSession(username); @@ -185,7 +185,7 @@ public class ContactListView extends LinearLayout { manager.createChatSession(username); } - Uri data = ContentUris.withAppendedId(Im.Chats.CONTENT_URI, id); + Uri data = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, id); Intent i = new Intent(Intent.ACTION_VIEW, data); i.addCategory(ImApp.IMPS_CATEGORY); mScreen.startActivity(i); @@ -215,7 +215,7 @@ public class ContactListView extends LinearLayout { void endChat(Cursor c) { if(c != null) { - String username = c.getString(c.getColumnIndexOrThrow(Im.Contacts.USERNAME)); + String username = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); try { IChatSessionManager manager = mConn.getChatSessionManager(); IChatSession session = manager.getChatSession(username); @@ -239,8 +239,8 @@ public class ContactListView extends LinearLayout { public void viewContactPresence(Cursor c) { if (c != null) { - long id = c.getLong(c.getColumnIndexOrThrow(Im.Contacts._ID)); - Uri data = ContentUris.withAppendedId(Im.Contacts.CONTENT_URI, id); + long id = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts._ID)); + Uri data = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, id); Intent i = new Intent(Intent.ACTION_VIEW, data); mScreen.startActivity(i); } @@ -292,8 +292,8 @@ public class ContactListView extends LinearLayout { if (c == null) { mHandler.showAlert(R.string.error, R.string.select_contact); } else { - String nickname = c.getString(c.getColumnIndexOrThrow(Im.Contacts.NICKNAME)); - final String address = c.getString(c.getColumnIndexOrThrow(Im.Contacts.USERNAME)); + String nickname = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.NICKNAME)); + final String address = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener(){ public void onClick(DialogInterface dialog, int whichButton) { try { @@ -334,8 +334,8 @@ public class ContactListView extends LinearLayout { if (c == null) { mHandler.showAlert(R.string.error, R.string.select_contact); } else { - String nickname = c.getString(c.getColumnIndexOrThrow(Im.Contacts.NICKNAME)); - final String address = c.getString(c.getColumnIndexOrThrow(Im.Contacts.USERNAME)); + String nickname = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.NICKNAME)); + final String address = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); DialogInterface.OnClickListener confirmListener = new DialogInterface.OnClickListener(){ public void onClick(DialogInterface dialog, int whichButton) { try { @@ -394,7 +394,7 @@ public class ContactListView extends LinearLayout { if (cursor == null) { return null; } - return cursor.getString(cursor.getColumnIndexOrThrow(Im.ContactList.NAME)); + return cursor.getString(cursor.getColumnIndexOrThrow(Imps.ContactList.NAME)); } private void registerListeners() { @@ -432,12 +432,12 @@ public class ContactListView extends LinearLayout { int subscriptionType = cursor.getInt(ContactView.COLUMN_SUBSCRIPTION_TYPE); int subscriptionStatus = cursor.getInt(ContactView.COLUMN_SUBSCRIPTION_STATUS); - if ((subscriptionType == Im.Contacts.SUBSCRIPTION_TYPE_FROM) - && (subscriptionStatus == Im.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING)){ + if ((subscriptionType == Imps.Contacts.SUBSCRIPTION_TYPE_FROM) + && (subscriptionStatus == Imps.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING)){ long providerId = cursor.getLong(ContactView.COLUMN_CONTACT_PROVIDER); String username = cursor.getString(ContactView.COLUMN_CONTACT_USERNAME); Intent intent = new Intent(ImServiceConstants.ACTION_MANAGE_SUBSCRIPTION, - ContentUris.withAppendedId(Im.Contacts.CONTENT_URI, id)); + ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, id)); intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, providerId); intent.putExtra(ImServiceConstants.EXTRA_INTENT_FROM_ADDRESS, username); mScreen.startActivity(intent); diff --git a/src/com/android/im/app/ContactPresenceActivity.java b/src/com/android/im/app/ContactPresenceActivity.java index 6531503..55454d4 100644 --- a/src/com/android/im/app/ContactPresenceActivity.java +++ b/src/com/android/im/app/ContactPresenceActivity.java @@ -25,7 +25,6 @@ import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; -import android.provider.Im; import android.text.SpannableString; import android.text.TextUtils; import android.text.style.ImageSpan; @@ -36,6 +35,7 @@ import android.widget.TextView; import com.android.im.R; import com.android.im.plugin.BrandingResourceIDs; +import com.android.im.provider.Imps; public class ContactPresenceActivity extends Activity { @@ -70,19 +70,19 @@ public class ContactPresenceActivity extends Activity { } if(c.moveToFirst()) { - long providerId = c.getLong(c.getColumnIndexOrThrow(Im.Contacts.PROVIDER)); - String username = c.getString(c.getColumnIndexOrThrow(Im.Contacts.USERNAME)); - String nickname = c.getString(c.getColumnIndexOrThrow(Im.Contacts.NICKNAME)); - int status = c.getInt(c.getColumnIndexOrThrow(Im.Contacts.PRESENCE_STATUS)); - int clientType = c.getInt(c.getColumnIndexOrThrow(Im.Contacts.CLIENT_TYPE)); - String customStatus = c.getString(c.getColumnIndexOrThrow(Im.Contacts.PRESENCE_CUSTOM_STATUS)); + long providerId = c.getLong(c.getColumnIndexOrThrow(Imps.Contacts.PROVIDER)); + String username = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.USERNAME)); + String nickname = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.NICKNAME)); + int status = c.getInt(c.getColumnIndexOrThrow(Imps.Contacts.PRESENCE_STATUS)); + int clientType = c.getInt(c.getColumnIndexOrThrow(Imps.Contacts.CLIENT_TYPE)); + String customStatus = c.getString(c.getColumnIndexOrThrow(Imps.Contacts.PRESENCE_CUSTOM_STATUS)); ImApp app = ImApp.getApplication(this); BrandingResources brandingRes = app.getBrandingResource(providerId); setTitle(brandingRes.getString(BrandingResourceIDs.STRING_CONTACT_INFO_TITLE)); Drawable avatar = DatabaseUtils.getAvatarFromCursor(c, - c.getColumnIndexOrThrow(Im.Contacts.AVATAR_DATA)); + c.getColumnIndexOrThrow(Imps.Contacts.AVATAR_DATA)); if (avatar != null) { imgAvatar.setImageDrawable(avatar); } else { @@ -119,7 +119,7 @@ public class ContactPresenceActivity extends Activity { private String getClientTypeString(int clientType) { Resources res = getResources(); switch (clientType) { - case Im.Contacts.CLIENT_TYPE_MOBILE: + case Imps.Contacts.CLIENT_TYPE_MOBILE: return res.getString(R.string.client_type_mobile); default: diff --git a/src/com/android/im/app/ContactView.java b/src/com/android/im/app/ContactView.java index 38ee6cf..08bf921 100644 --- a/src/com/android/im/app/ContactView.java +++ b/src/com/android/im/app/ContactView.java @@ -24,7 +24,6 @@ import android.content.Context; import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; -import android.provider.Im; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; @@ -40,24 +39,25 @@ import android.graphics.drawable.Drawable; import com.android.im.R; import com.android.im.plugin.BrandingResourceIDs; +import com.android.im.provider.Imps; import java.text.DateFormat; import java.util.Calendar; public class ContactView extends LinearLayout { static final String[] CONTACT_PROJECTION = { - Im.Contacts._ID, - Im.Contacts.PROVIDER, - Im.Contacts.ACCOUNT, - Im.Contacts.USERNAME, - Im.Contacts.NICKNAME, - Im.Contacts.TYPE, - Im.Contacts.SUBSCRIPTION_TYPE, - Im.Contacts.SUBSCRIPTION_STATUS, - Im.Presence.PRESENCE_STATUS, - Im.Presence.PRESENCE_CUSTOM_STATUS, - Im.Chats.LAST_MESSAGE_DATE, - Im.Chats.LAST_UNREAD_MESSAGE, + Imps.Contacts._ID, + Imps.Contacts.PROVIDER, + Imps.Contacts.ACCOUNT, + Imps.Contacts.USERNAME, + Imps.Contacts.NICKNAME, + Imps.Contacts.TYPE, + Imps.Contacts.SUBSCRIPTION_TYPE, + Imps.Contacts.SUBSCRIPTION_STATUS, + Imps.Presence.PRESENCE_STATUS, + Imps.Presence.PRESENCE_CUSTOM_STATUS, + Imps.Chats.LAST_MESSAGE_DATE, + Imps.Chats.LAST_UNREAD_MESSAGE, }; static final int COLUMN_CONTACT_ID = 0; @@ -116,7 +116,7 @@ public class ContactView extends LinearLayout { // status icon - if (Im.Contacts.TYPE_GROUP == type) { + if (Imps.Contacts.TYPE_GROUP == type) { iconId = lastMsg == null ? R.drawable.group_chat : R.drawable.group_chat_new; } else if (hasChat) { iconId = lastMsg == null ? BrandingResourceIDs.DRAWABLE_READ_CHAT @@ -130,7 +130,7 @@ public class ContactView extends LinearLayout { // line1 CharSequence line1; - if (Im.Contacts.TYPE_GROUP == type) { + if (Imps.Contacts.TYPE_GROUP == type) { ContentResolver resolver = getContext().getContentResolver(); long id = cursor.getLong(ContactView.COLUMN_CONTACT_ID); line1 = queryGroupMembers(resolver, id); @@ -151,7 +151,7 @@ public class ContactView extends LinearLayout { } } - if (Im.Contacts.TYPE_TEMPORARY == type) { + if (Imps.Contacts.TYPE_TEMPORARY == type) { // Add a mark at the front of name if it's only a temporary // contact. SpannableStringBuilder str = new SpannableStringBuilder( @@ -182,7 +182,7 @@ public class ContactView extends LinearLayout { } if (TextUtils.isEmpty(line2)){ - if (Im.Contacts.TYPE_GROUP == type) { + if (Imps.Contacts.TYPE_GROUP == type) { // Show nothing in line2 if it's a group and don't // have any unread message. line2 = null; @@ -214,8 +214,8 @@ public class ContactView extends LinearLayout { } private String queryGroupMembers(ContentResolver resolver, long groupId) { - String[] projection = { Im.GroupMembers.NICKNAME }; - Uri uri = ContentUris.withAppendedId(Im.GroupMembers.CONTENT_URI, groupId); + String[] projection = { Imps.GroupMembers.NICKNAME }; + Uri uri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, groupId); Cursor c = resolver.query(uri, projection, null, null, null); StringBuilder buf = new StringBuilder(); if(c != null) { diff --git a/src/com/android/im/app/ContactsPickerActivity.java b/src/com/android/im/app/ContactsPickerActivity.java index eb52aa3..0dfb287 100644 --- a/src/com/android/im/app/ContactsPickerActivity.java +++ b/src/com/android/im/app/ContactsPickerActivity.java @@ -18,6 +18,7 @@ package com.android.im.app; import com.android.im.R; +import com.android.im.provider.Imps; import android.app.ListActivity; import android.content.Context; @@ -26,7 +27,6 @@ import android.database.Cursor; import android.database.DatabaseUtils; import android.net.Uri; import android.os.Bundle; -import android.provider.Im; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; @@ -89,7 +89,7 @@ public class ContactsPickerActivity extends ListActivity { } mExcludeClause = buildExcludeClause(i.getStringArrayExtra(EXTRA_EXCLUDED_CONTACTS)); Cursor cursor = managedQuery(mData, ContactView.CONTACT_PROJECTION, - mExcludeClause, Im.Contacts.DEFAULT_SORT_ORDER); + mExcludeClause, Imps.Contacts.DEFAULT_SORT_ORDER); if (cursor == null) { return false; } @@ -116,7 +116,7 @@ public class ContactsPickerActivity extends ListActivity { } StringBuilder clause = new StringBuilder(); - clause.append(Im.Contacts.USERNAME); + clause.append(Imps.Contacts.USERNAME); clause.append(" NOT IN ("); int len = excluded.length; for (int i = 0; i < len - 1; i++) { @@ -138,14 +138,14 @@ public class ContactsPickerActivity extends ListActivity { buf.append(mExcludeClause).append(" AND "); } - buf.append(Im.Contacts.NICKNAME); + buf.append(Imps.Contacts.NICKNAME); buf.append(" LIKE "); DatabaseUtils.appendValueToSql(buf, "%" + constraint + "%"); where = buf.toString(); } return managedQuery(mData, ContactView.CONTACT_PROJECTION, where, - Im.Contacts.DEFAULT_SORT_ORDER); + Imps.Contacts.DEFAULT_SORT_ORDER); } private class ContactsAdapter extends ResourceCursorAdapter { diff --git a/src/com/android/im/app/DatabaseUtils.java b/src/com/android/im/app/DatabaseUtils.java index 503f9be..4484114 100644 --- a/src/com/android/im/app/DatabaseUtils.java +++ b/src/com/android/im/app/DatabaseUtils.java @@ -18,6 +18,7 @@ package com.android.im.app; import com.android.im.plugin.ImConfigNames; +import com.android.im.provider.Imps; import android.content.ContentResolver; import android.content.ContentUris; @@ -28,7 +29,6 @@ import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; -import android.provider.Im; import android.util.Log; import java.util.Map; @@ -42,9 +42,9 @@ public class DatabaseUtils { public static Cursor queryAccountsForProvider(ContentResolver cr, String[] projection, long providerId) { - StringBuilder where = new StringBuilder(Im.Account.ACTIVE); - where.append("=1 AND ").append(Im.Account.PROVIDER).append('=').append(providerId); - Cursor c = cr.query(Im.Account.CONTENT_URI, projection, where.toString(), null, null); + StringBuilder where = new StringBuilder(Imps.Account.ACTIVE); + where.append("=1 AND ").append(Imps.Account.PROVIDER).append('=').append(providerId); + Cursor c = cr.query(Imps.Account.CONTENT_URI, projection, where.toString(), null, null); if (c != null && !c.moveToFirst()) { c.close(); return null; @@ -116,9 +116,9 @@ public class DatabaseUtils { private static void updateAvatarBlob(ContentResolver resolver, Uri updateUri, byte[] data, String username) { ContentValues values = new ContentValues(3); - values.put(Im.Avatars.DATA, data); + values.put(Imps.Avatars.DATA, data); - StringBuilder buf = new StringBuilder(Im.Avatars.CONTACT); + StringBuilder buf = new StringBuilder(Imps.Avatars.CONTACT); buf.append("=?"); String[] selectionArgs = new String[] { @@ -149,7 +149,7 @@ public class DatabaseUtils { boolean versionChanged; // query provider data - long providerId = Im.Provider.getProviderIdForName(cr, providerName); + long providerId = Imps.Provider.getProviderIdForName(cr, providerName); if (providerId > 0) { // already loaded, check if version changed String pluginVersion = config.get(ImConfigNames.PLUGIN_VERSION); @@ -183,10 +183,10 @@ public class DatabaseUtils { */ private static int clearBrandingResourceMapCache(ContentResolver cr, long providerId) { StringBuilder where = new StringBuilder(); - where.append(Im.BrandingResourceMapCache.PROVIDER_ID); + where.append(Imps.BrandingResourceMapCache.PROVIDER_ID); where.append('='); where.append(providerId); - return cr.delete(Im.BrandingResourceMapCache.CONTENT_URI, where.toString(), null); + return cr.delete(Imps.BrandingResourceMapCache.CONTENT_URI, where.toString(), null); } /** @@ -198,12 +198,12 @@ public class DatabaseUtils { int index = 0; for (Map.Entry entry : config.entrySet()) { ContentValues settingValue = new ContentValues(); - settingValue.put(Im.ProviderSettings.PROVIDER, providerId); - settingValue.put(Im.ProviderSettings.NAME, entry.getKey()); - settingValue.put(Im.ProviderSettings.VALUE, entry.getValue()); + settingValue.put(Imps.ProviderSettings.PROVIDER, providerId); + settingValue.put(Imps.ProviderSettings.NAME, entry.getKey()); + settingValue.put(Imps.ProviderSettings.VALUE, entry.getValue()); settingValues[index++] = settingValue; } - return cr.bulkInsert(Im.ProviderSettings.CONTENT_URI, settingValues); + return cr.bulkInsert(Imps.ProviderSettings.CONTENT_URI, settingValues); } /** @@ -212,11 +212,11 @@ public class DatabaseUtils { private static long insertProviderRow(ContentResolver cr, String providerName, String providerFullName, String signUpUrl) { ContentValues values = new ContentValues(3); - values.put(Im.Provider.NAME, providerName); - values.put(Im.Provider.FULLNAME, providerFullName); - values.put(Im.Provider.CATEGORY, ImApp.IMPS_CATEGORY); - values.put(Im.Provider.SIGNUP_URL, signUpUrl); - Uri result = cr.insert(Im.Provider.CONTENT_URI, values); + values.put(Imps.Provider.NAME, providerName); + values.put(Imps.Provider.FULLNAME, providerFullName); + values.put(Imps.Provider.CATEGORY, ImApp.IMPS_CATEGORY); + values.put(Imps.Provider.SIGNUP_URL, signUpUrl); + Uri result = cr.insert(Imps.Provider.CONTENT_URI, values); return ContentUris.parseId(result); } @@ -231,10 +231,10 @@ public class DatabaseUtils { // Note that we don't update the provider name because it's used as // identifier at some place and the plugin should never change it. ContentValues values = new ContentValues(3); - values.put(Im.Provider.FULLNAME, providerFullName); - values.put(Im.Provider.SIGNUP_URL, signUpUrl); - values.put(Im.Provider.CATEGORY, ImApp.IMPS_CATEGORY); - Uri uri = ContentUris.withAppendedId(Im.Provider.CONTENT_URI, providerId); + values.put(Imps.Provider.FULLNAME, providerFullName); + values.put(Imps.Provider.SIGNUP_URL, signUpUrl); + values.put(Imps.Provider.CATEGORY, ImApp.IMPS_CATEGORY); + Uri uri = ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId); return cr.update(uri, values, null, null); } @@ -243,7 +243,7 @@ public class DatabaseUtils { */ private static boolean isPluginVersionChanged(ContentResolver cr, long providerId, String newVersion) { - String oldVersion = Im.ProviderSettings.getStringValue(cr, providerId, + String oldVersion = Imps.ProviderSettings.getStringValue(cr, providerId, ImConfigNames.PLUGIN_VERSION); if (oldVersion == null) { return true; diff --git a/src/com/android/im/app/FrontDoorPlugin.java b/src/com/android/im/app/FrontDoorPlugin.java deleted file mode 100644 index fedd45e..0000000 --- a/src/com/android/im/app/FrontDoorPlugin.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * 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.im.app; - -import com.android.im.plugin.ImConfigNames; -import com.android.im.plugin.ImPluginConstants; - -import android.app.Service; -import android.content.Intent; -import android.content.ContentUris; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.im.IImPlugin; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.Bundle; -import android.provider.Im; -import android.util.Log; -import android.text.TextUtils; -import android.database.Cursor; -import android.net.Uri; - -import java.util.HashMap; -import java.util.Map; -import java.util.List; -import java.util.ArrayList; -import java.lang.reflect.Method; -import java.lang.reflect.InvocationTargetException; - -import dalvik.system.PathClassLoader; - - -public class FrontDoorPlugin extends Service { - private final static String TAG = ImApp.LOG_TAG; - private final static boolean LOCAL_DEBUG = false; - - // database access constants for branding resource map cache table - private final static String[] BRANDING_RESOURCE_MAP_CACHE_PROJECTION = { - Im.BrandingResourceMapCache.PROVIDER_ID, - Im.BrandingResourceMapCache.APP_RES_ID, - Im.BrandingResourceMapCache.PLUGIN_RES_ID - }; - private final static int BRANDING_RESOURCE_MAP_CACHE_PROVIDER_ID_COLUMN = 0; - private final static int BRANDING_RESOURCE_MAP_CACHE_APP_RES_ID_COLUMN = 1; - private final static int BRANDING_RESOURCE_MAP_CACHE_PLUGIN_RES_ID_COLUMN = 2; - - private ArrayList mProviderNames; - private HashMap mPackageNames; - private HashMap> mBrandingResources; - - @Override - public IBinder onBind(Intent intent) { - // temporary mappings - HashMap providerNameToId = new HashMap(); - HashMap providerIdToName = new HashMap(); - HashMap classes = new HashMap(); - - loadThirdPartyPlugins(providerNameToId, providerIdToName, classes); - loadBrandingResources(providerNameToId, providerIdToName, classes); - - return mBinder; - } - - private void loadThirdPartyPlugins( - HashMap providerNameToId, HashMap providerIdToName, - HashMap classes) { - mProviderNames = new ArrayList(); - mPackageNames = new HashMap(); - - PackageManager pm = getPackageManager(); - List plugins = pm.queryIntentServices( - new Intent(ImPluginConstants.PLUGIN_ACTION_NAME), PackageManager.GET_META_DATA); - int numPlugins = plugins.size(); - - if (Log.isLoggable(TAG, Log.DEBUG)) { - log("loadThirdPartyPlugins: # plugins found: " + numPlugins); - } - - if (numPlugins == 0) { - Log.e(TAG, "[IM.FrontDoorPlugin] no plugins found! bail..."); - return; - } - - for (ResolveInfo info : plugins) { - if (Log.isLoggable(TAG, Log.DEBUG)) log("loadThirdPartyPlugins: found plugin " + info); - - ServiceInfo serviceInfo = info.serviceInfo; - if (serviceInfo == null) { - Log.e(TAG, "[FrontDoorPlugin] loadThirdPartyPlugins: ignore bad plugin: " + info); - continue; - } - - String providerName = null; - String providerFullName = null; - String signUpUrl = null; - Bundle metaData = serviceInfo.metaData; - if (metaData != null) { - providerName = metaData.getString(ImPluginConstants.METADATA_PROVIDER_NAME); - providerFullName = - metaData.getString(ImPluginConstants.METADATA_PROVIDER_FULL_NAME); - signUpUrl = metaData.getString(ImPluginConstants.METADATA_SIGN_UP_URL); - } - if (TextUtils.isEmpty(providerName) || TextUtils.isEmpty(providerFullName)) { - Log.e(TAG, "[FrontDoorPlugin] Ignore bad IM plugin: " + info + - ". Lack of required meta data"); - continue; - } - - mProviderNames.add(providerName); - mPackageNames.put(providerName, serviceInfo.packageName); - - String className = serviceInfo.name; - String srcPath = serviceInfo.applicationInfo.sourceDir; - Class pluginClass = loadClass(className, srcPath); - if (pluginClass == null) { - Log.e(TAG, "[FrontDoorPlugin] Can not load package for plugin " + providerName); - continue; - } - classes.put(providerName, pluginClass); - - Map config = loadProviderConfigFromPlugin(pluginClass); - if (config == null) { - Log.e(TAG, "[FrontDoorPlugin] Can not load config for plugin " + providerName); - continue; - } - config.put(ImConfigNames.PLUGIN_PATH, srcPath); - config.put(ImConfigNames.PLUGIN_CLASS, className); - - long providerId = DatabaseUtils.updateProviderDb(getContentResolver(), - providerName, providerFullName, signUpUrl, config); - providerNameToId.put(providerName, providerId); - providerIdToName.put(providerId, providerName); - } - } - - private void loadBrandingResources( - HashMap providerNameToId, HashMap providerIdToName, - HashMap classes) { - mBrandingResources = new HashMap>(); - - // first try load from cache - loadBrandingResourcesFromCache(providerIdToName); - - // check and load any un-cached resources - final ArrayList valuesList = new ArrayList(); - for (String provider : mProviderNames) { - long providerId = providerNameToId.get(provider); - if (!mBrandingResources.containsKey(provider)) { - Map resMap = loadBrandingResource(classes.get(provider)); - if (resMap != null) { - mBrandingResources.put(provider, resMap); - for (int appResId : resMap.keySet()) { - int pluginResId = resMap.get(appResId); - - ContentValues values = new ContentValues(); - values.put(Im.BrandingResourceMapCache.PROVIDER_ID, providerId); - values.put(Im.BrandingResourceMapCache.APP_RES_ID, appResId); - values.put(Im.BrandingResourceMapCache.PLUGIN_RES_ID, pluginResId); - - valuesList.add(values); - } - Log.d(TAG, "Plugin " + provider + " not in cache, loaded and saved"); - } - } - } - - // save the changes to cache - if (valuesList.size() > 0) { - new Thread(new Runnable() { - public void run() { - getContentResolver().bulkInsert( - Im.BrandingResourceMapCache.CONTENT_URI, - valuesList.toArray(new ContentValues[]{})); - } - }).start(); - } - } - - /** - * Try loading the branding resources from the database. - * @param providerIdToName a map between provider ID and name. - */ - private void loadBrandingResourcesFromCache(HashMap providerIdToName) { - ContentResolver cr = getContentResolver(); - Cursor c = cr.query( - Im.BrandingResourceMapCache.CONTENT_URI, /* URI */ - BRANDING_RESOURCE_MAP_CACHE_PROJECTION, /* projection */ - null, /* where */ - null, /* where args */ - null /* sort */); - - if (c != null) { - try { - while (c.moveToNext()) { - long providerId = c.getLong(BRANDING_RESOURCE_MAP_CACHE_PROVIDER_ID_COLUMN); - String provider = providerIdToName.get(providerId); - if (TextUtils.isEmpty(provider)) { - Log.e(TAG, "Empty provider name in branding resource map cache table."); - continue; - } - int appResId = c.getInt(BRANDING_RESOURCE_MAP_CACHE_APP_RES_ID_COLUMN); - int pluginResId = c.getInt(BRANDING_RESOURCE_MAP_CACHE_PLUGIN_RES_ID_COLUMN); - - Map resMap = mBrandingResources.get(provider); - if (resMap == null) { - resMap = new HashMap(); - mBrandingResources.put(provider, resMap); - } - - resMap.put(appResId, pluginResId); - } - } finally { - c.close(); - } - } else { - Log.e(TAG, "Query of branding resource map cache table returns empty cursor"); - } - } - - /** - * Load branding resources from one plugin. - */ - private Map loadBrandingResource(Class cls) { - try { - Method m = cls.getMethod("getResourceMap"); - // TODO: this would still cause a VM verifier exception to be thrown if. - // the landing page Android.mk and AndroidManifest.xml don't include use-library for - // "com.android.im.plugin". This is even with getCustomClassLoader() as the parent - // class loader. - return (Map)m.invoke(cls.newInstance(), new Object[]{}); - - } catch (IllegalAccessException e) { - Log.e(TAG, "Failed load the plugin resource map", e); - } catch (InstantiationException e) { - Log.e(TAG, "Failed load the plugin resource map", e); - } catch (SecurityException e) { - Log.e(TAG, "Failed load the plugin resource map", e); - } catch (NoSuchMethodException e) { - Log.e(TAG, "Failed load the plugin resource map", e); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Failed load the plugin resource map", e); - } catch (InvocationTargetException e) { - Log.e(TAG, "Failed load the plugin resource map", e); - } - return null; - } - - /** - * Load plugin config. - */ - private Map loadProviderConfigFromPlugin(Class cls) { - try { - Method m = cls.getMethod("onBind", Intent.class); - com.android.im.plugin.IImPlugin plugin = - (com.android.im.plugin.IImPlugin)m.invoke(cls.newInstance(), new Object[]{null}); - return plugin.getProviderConfig(); - } catch (IllegalAccessException e) { - Log.e(TAG, "Could not create plugin instance", e); - } catch (InstantiationException e) { - Log.e(TAG, "Could not create plugin instance", e); - } catch (SecurityException e) { - Log.e(TAG, "Could not load config from the plugin", e); - } catch (NoSuchMethodException e) { - Log.e(TAG, "Could not load config from the plugin", e); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Could not load config from the plugin", e); - } catch (InvocationTargetException e) { - Log.e(TAG, "Could not load config from the plugin", e); - } catch (RemoteException e) { - Log.e(TAG, "Could not load config from the plugin", e); - } - return null; - } - - private Class loadClass(String className, String srcPath) { - PathClassLoader loader = new PathClassLoader(srcPath, getClassLoader()); - try { - return loader.loadClass(className); - } catch (ClassNotFoundException e) { - Log.e(TAG, "Could not find plugin class", e); - } - return null; - } - - private void log(String msg) { - Log.d(TAG, "[ImFrontDoor] " + msg); - } - - - /** - * The implementation of IImFrontDoorPlugin defined through AIDL. - */ - private final IImPlugin.Stub mBinder = new IImPlugin.Stub() { - - /** - * Notify the plugin the front door activity is created. This gives the plugin a chance to - * start its own servics, etc. - */ - public void onStart() { - } - - /** - * Notify the plugin the front door activity is stopping. - */ - public void onStop() { - } - - /** - * Sign in to the service for the account passed in. - */ - public void signIn(long account) { - if (LOCAL_DEBUG) log("signIn for account " + account); - - Intent intent = new Intent(); - intent.setData(ContentUris.withAppendedId(Im.Account.CONTENT_URI, account)); - intent.setClassName("com.android.im", "com.android.im.app.SigningInActivity"); - - startActivity(intent); - } - - /** - * Sign out of the service for the account passed in. - */ - public void signOut(long account) { - if (LOCAL_DEBUG) log("signOut for account " + account); - Intent intent = new Intent(); - intent.setData(ContentUris.withAppendedId(Im.Account.CONTENT_URI, account)); - intent.setClassName("com.android.im", "com.android.im.app.SignoutActivity"); - - startActivity(intent); - } - - public String getResourcePackageNameForProvider(String providerName) { - return mPackageNames.get(providerName); - } - - public Map getResourceMapForProvider(String providerName) throws RemoteException { - return mBrandingResources.get(providerName); - } - - public List getSupportedProviders() { - return mProviderNames; - } - }; - -} diff --git a/src/com/android/im/app/ImApp.java b/src/com/android/im/app/ImApp.java index 86acc7c..02f9cae 100644 --- a/src/com/android/im/app/ImApp.java +++ b/src/com/android/im/app/ImApp.java @@ -17,6 +17,12 @@ package com.android.im.app; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + import android.app.Activity; import android.app.Application; import android.content.ComponentName; @@ -27,20 +33,16 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.Uri; import android.os.Broadcaster; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; -import android.provider.Im; -import android.text.TextUtils; import android.util.Log; import com.android.im.IConnectionCreationListener; @@ -51,15 +53,11 @@ import com.android.im.app.adapter.ConnectionListenerAdapter; import com.android.im.engine.ImConnection; import com.android.im.engine.ImErrorInfo; import com.android.im.plugin.BrandingResourceIDs; -import com.android.im.plugin.ImPluginConstants; +import com.android.im.plugin.ImPlugin; import com.android.im.plugin.ImPluginInfo; +import com.android.im.provider.Imps; import com.android.im.service.ImServiceConstants; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; - public class ImApp extends Application { public static final String LOG_TAG = "ImApp"; @@ -100,18 +98,18 @@ public class ImApp extends Application { public static final int EVENT_UPDATE_USER_PRESENCE_ERROR = 301; private static final String[] PROVIDER_PROJECTION = { - Im.Provider._ID, - Im.Provider.NAME, - Im.Provider.FULLNAME, - Im.Provider.SIGNUP_URL, + Imps.Provider._ID, + Imps.Provider.NAME, + Imps.Provider.FULLNAME, + Imps.Provider.SIGNUP_URL, }; private static final String[] ACCOUNT_PROJECTION = { - Im.Account._ID, - Im.Account.PROVIDER, - Im.Account.NAME, - Im.Account.USERNAME, - Im.Account.PASSWORD, + Imps.Account._ID, + Imps.Account.PROVIDER, + Imps.Account.NAME, + Imps.Account.USERNAME, + Imps.Account.PASSWORD, }; static final void log(String log) { @@ -173,8 +171,6 @@ public class ImApp extends Application { super.onCreate(); mBroadcaster = new Broadcaster(); loadDefaultBrandingRes(); - mBrandingResources = new HashMap(); - loadThirdPartyResources(); } @Override @@ -265,27 +261,27 @@ public class ImApp extends Application { public static long insertOrUpdateAccount(ContentResolver cr, long providerId, String userName, String pw) { - String selection = Im.Account.PROVIDER + "=? AND " + Im.Account.USERNAME + "=?"; + String selection = Imps.Account.PROVIDER + "=? AND " + Imps.Account.USERNAME + "=?"; String[] selectionArgs = {Long.toString(providerId), userName }; - Cursor c = cr.query(Im.Account.CONTENT_URI, ACCOUNT_PROJECTION, + Cursor c = cr.query(Imps.Account.CONTENT_URI, ACCOUNT_PROJECTION, selection, selectionArgs, null); if (c != null && c.moveToFirst()) { // Update the password - c.updateString(c.getColumnIndexOrThrow(Im.Account.PASSWORD), pw); + c.updateString(c.getColumnIndexOrThrow(Imps.Account.PASSWORD), pw); c.commitUpdates(); - long id = c.getLong(c.getColumnIndexOrThrow(Im.Account._ID)); + long id = c.getLong(c.getColumnIndexOrThrow(Imps.Account._ID)); c.close(); return id; } else { ContentValues values = new ContentValues(4); - values.put(Im.Account.PROVIDER, providerId); - values.put(Im.Account.NAME, userName); - values.put(Im.Account.USERNAME, userName); - values.put(Im.Account.PASSWORD, pw); + values.put(Imps.Account.PROVIDER, providerId); + values.put(Imps.Account.NAME, userName); + values.put(Imps.Account.USERNAME, userName); + values.put(Imps.Account.PASSWORD, pw); - Uri result = cr.insert(Im.Account.CONTENT_URI, values); + Uri result = cr.insert(Imps.Account.CONTENT_URI, values); return ContentUris.parseId(result); } } @@ -294,15 +290,15 @@ public class ImApp extends Application { if (mProviders != null) { return; } - + mProviders = new HashMap(); ContentResolver cr = getContentResolver(); String selectionArgs[] = new String[1]; selectionArgs[0] = ImApp.IMPS_CATEGORY; - Cursor c = cr.query(Im.Provider.CONTENT_URI, PROVIDER_PROJECTION, - Im.Provider.CATEGORY+"=?", selectionArgs, null); + Cursor c = cr.query(Imps.Provider.CONTENT_URI, PROVIDER_PROJECTION, + Imps.Provider.CATEGORY+"=?", selectionArgs, null); if (c == null) { return; } @@ -401,40 +397,28 @@ public class ImApp extends Application { } private void loadThirdPartyResources() { + ImPluginHelper helper = ImPluginHelper.getInstance(this); + helper.loadAvaiablePlugins(); + ArrayList pluginList = helper.getPluginObjects(); + ArrayList infoList = helper.getPluginsInfo(); + int N = pluginList.size(); PackageManager pm = getPackageManager(); - List plugins = pm.queryIntentServices( - new Intent(ImPluginConstants.PLUGIN_ACTION_NAME), PackageManager.GET_META_DATA); - for (ResolveInfo info : plugins) { - Log.d(LOG_TAG, "Found plugin " + info); - - ServiceInfo serviceInfo = info.serviceInfo; - if (serviceInfo == null) { - Log.e(LOG_TAG, "Ignore bad IM plugin: " + info); - continue; - } - String providerName = null; - String providerFullName = null; - Bundle metaData = serviceInfo.metaData; - if (metaData != null) { - providerName = metaData.getString( - ImPluginConstants.METADATA_PROVIDER_NAME); - providerFullName = metaData.getString( - ImPluginConstants.METADATA_PROVIDER_FULL_NAME); - } - if (TextUtils.isEmpty(providerName) - || TextUtils.isEmpty(providerFullName)) { - Log.e(LOG_TAG, "Ignore bad IM plugin: " + info - + ". Lack of required meta data"); - continue; - } + for (int i = 0; i < N; i++) { + ImPlugin plugin = pluginList.get(i); + ImPluginInfo pluginInfo = infoList.get(i); + + try { + Resources packageRes = pm.getResourcesForApplication(pluginInfo.mPackageName); - ImPluginInfo pluginInfo = new ImPluginInfo(providerName, - serviceInfo.packageName, serviceInfo.name, - serviceInfo.applicationInfo.sourceDir); + Map resMap = plugin.getResourceMap(); + int[] smileyIcons = plugin.getSmileyIconIds(); - BrandingResources res = new BrandingResources(this, pluginInfo, - mDefaultBrandingResources); - mBrandingResources.put(providerName, res); + BrandingResources res = new BrandingResources(packageRes, resMap, + smileyIcons, mDefaultBrandingResources); + mBrandingResources.put(pluginInfo.mProviderName, res); + } catch (NameNotFoundException e) { + Log.e(LOG_TAG, "Failed to load third party resources.", e); + } } } @@ -465,6 +449,10 @@ public class ImApp extends Application { if (provider == null) { return mDefaultBrandingResources; } + if (mBrandingResources == null) { + mBrandingResources = new HashMap(); + loadThirdPartyResources(); + } BrandingResources res = mBrandingResources.get(provider.mName); return res == null ? mDefaultBrandingResources : res; } @@ -670,6 +658,9 @@ public class ImApp extends Application { case ImConnection.LOGGING_OUT: what = EVENT_CONNECTION_LOGGING_OUT; + synchronized (mConnections) { + mConnections.remove(providerId); + } break; case ImConnection.DISCONNECTED: diff --git a/src/com/android/im/app/ImPluginHelper.java b/src/com/android/im/app/ImPluginHelper.java new file mode 100644 index 0000000..4e9159e --- /dev/null +++ b/src/com/android/im/app/ImPluginHelper.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2009 Myriad Group AG. + * 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. + */ + +package com.android.im.app; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.database.Cursor; +import android.database.sqlite.SQLiteFullException; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; + +import com.android.im.plugin.ImConfigNames; +import com.android.im.plugin.ImPlugin; +import com.android.im.plugin.ImPluginConstants; +import com.android.im.plugin.ImPluginInfo; +import com.android.im.provider.Imps; + +public class ImPluginHelper { + + private static final String TAG = "ImPluginUtils"; + + private Context mContext; + private ArrayList mPluginsInfo; + private ArrayList mPluginObjects; + private boolean mLoaded; + + private static ImPluginHelper sInstance; + public static ImPluginHelper getInstance(Context context) { + if (sInstance == null) { + sInstance = new ImPluginHelper(context); + } + return sInstance; + } + + private ImPluginHelper(Context context) { + mContext = context; + mPluginsInfo = new ArrayList(); + mPluginObjects = new ArrayList(); + } + + public ArrayList getPluginsInfo() { + if (!mLoaded) { + loadAvaiablePlugins(); + } + return mPluginsInfo; + } + + public ArrayList getPluginObjects() { + if (!mLoaded) { + loadAvaiablePlugins(); + } + return mPluginObjects; + } + + public void loadAvaiablePlugins() { + if (mLoaded) { + return; + } + + PackageManager pm = mContext.getPackageManager(); + List plugins = pm.queryIntentServices( + new Intent(ImPluginConstants.PLUGIN_ACTION_NAME), PackageManager.GET_META_DATA); + for (ResolveInfo info : plugins) { + Log.d(TAG, "Found plugin " + info); + + ServiceInfo serviceInfo = info.serviceInfo; + if (serviceInfo == null) { + Log.e(TAG, "Ignore bad IM plugin: " + info); + continue; + } + String providerName = null; + String providerFullName = null; + String signUpUrl = null; + Bundle metaData = serviceInfo.metaData; + if (metaData != null) { + providerName = metaData.getString(ImPluginConstants.METADATA_PROVIDER_NAME); + providerFullName = metaData.getString(ImPluginConstants.METADATA_PROVIDER_FULL_NAME); + signUpUrl = metaData.getString(ImPluginConstants.METADATA_SIGN_UP_URL); + } + if (TextUtils.isEmpty(providerName) || TextUtils.isEmpty(providerFullName)) { + Log.e(TAG, "Ignore bad IM plugin: " + info + ". Lack of required meta data"); + continue; + } + + if (isPluginDuplicated(providerName)) { + Log.e(TAG, "Ignore duplicated IM plugin: " + info); + continue; + } + + if (!serviceInfo.packageName.equals(mContext.getPackageName())) { + Log.e(TAG, "Ignore plugin in package: " + serviceInfo.packageName); + continue; + } + ImPluginInfo pluginInfo = new ImPluginInfo(providerName, serviceInfo.packageName, + serviceInfo.name, serviceInfo.applicationInfo.sourceDir); + + ImPlugin plugin = loadPlugin(pluginInfo); + if (plugin == null) { + Log.e(TAG, "Ignore bad IM plugin"); + continue; + } + + try { + updateProviderDb(plugin, pluginInfo,providerFullName, signUpUrl); + } catch (SQLiteFullException e) { + Log.e(TAG, "Storage full", e); + return; + } + mPluginsInfo.add(pluginInfo); + mPluginObjects.add(plugin); + } + mLoaded = true; + } + + private boolean isPluginDuplicated(String providerName) { + for (ImPluginInfo plugin : mPluginsInfo) { + if (plugin.mProviderName.equals(providerName)) { + return true; + } + } + return false; + } + + private ImPlugin loadPlugin(ImPluginInfo pluginInfo) { + // XXX Load the plug-in implementation directly from the apk rather than + // binding to the service and call through IPC Binder API. This is much + // more effective since we don't need to start the service in other + // process. We can not run the plug-in service in the same process as a + // local service because that the interface is defined in a shared + // library in order to compile the plug-in separately. In this case, the + // interface will be loaded by two class loader separately and a + // ClassCastException will be thrown if we cast the binder to the + // interface. + ClassLoader loader = mContext.getClassLoader(); + try { + Class cls = loader.loadClass(pluginInfo.mClassName); + return (ImPlugin) cls.newInstance(); + } catch (ClassNotFoundException e) { + Log.e(TAG, "Could not find plugin class", e); + } catch (IllegalAccessException e) { + Log.e(TAG, "Could not create plugin instance", e); + } catch (InstantiationException e) { + Log.e(TAG, "Could not create plugin instance", e); + } catch (SecurityException e) { + Log.e(TAG, "Could not load plugin", e); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Could not load plugin", e); + } + return null; + } + + private long updateProviderDb(ImPlugin plugin, ImPluginInfo info, + String providerFullName, String signUpUrl) { + Map config = loadConfiguration(plugin, info); + if (config == null) { + return 0; + } + + long providerId = 0; + ContentResolver cr = mContext.getContentResolver(); + String where = Imps.Provider.NAME + "=?"; + String[] selectionArgs = new String[]{info.mProviderName}; + Cursor c = cr.query(Imps.Provider.CONTENT_URI, + null /* projection */, + where, + selectionArgs, + null /* sort order */); + + boolean pluginChanged; + try { + if (c.moveToFirst()) { + providerId = c.getLong(c.getColumnIndexOrThrow(Imps.Provider._ID)); + pluginChanged = isPluginChanged(cr, providerId, config); + if (pluginChanged) { + // Update the full name, signup url and category each time when the plugin change + // instead of specific version change because this is called only once. + // It's ok to update them even the values are not changed. + // Note that we don't update the provider name because it's used as + // identifier at some place and the plugin should never change it. + ContentValues values = new ContentValues(3); + values.put(Imps.Provider.FULLNAME, providerFullName); + values.put(Imps.Provider.SIGNUP_URL, signUpUrl); + values.put(Imps.Provider.CATEGORY, ImApp.IMPS_CATEGORY); + Uri uri = ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId); + cr.update(uri, values, null, null); + } + } else { + ContentValues values = new ContentValues(3); + values.put(Imps.Provider.NAME, info.mProviderName); + values.put(Imps.Provider.FULLNAME, providerFullName); + values.put(Imps.Provider.CATEGORY, ImApp.IMPS_CATEGORY); + values.put(Imps.Provider.SIGNUP_URL, signUpUrl); + + Uri result = cr.insert(Imps.Provider.CONTENT_URI, values); + providerId = ContentUris.parseId(result); + pluginChanged = true; + } + } finally { + if (c != null) { + c.close(); + } + } + + if (pluginChanged) { + // Remove all the old settings + cr.delete(ContentUris.withAppendedId( + Imps.ProviderSettings.CONTENT_URI, providerId), + null, /*where*/ + null /*selectionArgs*/); + + ContentValues[] settingValues = new ContentValues[config.size()]; + + int index = 0; + for (Map.Entry entry : config.entrySet()) { + ContentValues settingValue = new ContentValues(); + settingValue.put(Imps.ProviderSettings.PROVIDER, providerId); + settingValue.put(Imps.ProviderSettings.NAME, entry.getKey()); + settingValue.put(Imps.ProviderSettings.VALUE, entry.getValue()); + settingValues[index++] = settingValue; + } + cr.bulkInsert(Imps.ProviderSettings.CONTENT_URI, settingValues); + } + + return providerId; + } + + private Map loadConfiguration(ImPlugin plugin, + ImPluginInfo info) { + Map config = null; + + config = plugin.getProviderConfig(); + + if (config != null) { + config.put(ImConfigNames.PLUGIN_PATH, info.mSrcPath); + config.put(ImConfigNames.PLUGIN_CLASS, info.mClassName); + } + return config; + } + + private boolean isPluginChanged(ContentResolver cr, long providerId, + Map config) { + String origVersion = Imps.ProviderSettings.getStringValue(cr, providerId, + ImConfigNames.PLUGIN_VERSION); + + if (origVersion == null) { + return true; + } + String newVersion = config.get(ImConfigNames.PLUGIN_VERSION); + return !origVersion.equals(newVersion); + } +} diff --git a/src/com/android/im/app/ImRingtonePreference.java b/src/com/android/im/app/ImRingtonePreference.java index 9f89c61..59183e6 100644 --- a/src/com/android/im/app/ImRingtonePreference.java +++ b/src/com/android/im/app/ImRingtonePreference.java @@ -17,6 +17,7 @@ package com.android.im.app; +import com.android.im.provider.Imps; import com.android.im.service.ImServiceConstants; import android.app.Activity; @@ -24,7 +25,6 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.preference.RingtonePreference; -import android.provider.Im; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; @@ -47,7 +47,7 @@ public class ImRingtonePreference extends RingtonePreference { @Override protected Uri onRestoreRingtone() { - final Im.ProviderSettings.QueryMap settings = new Im.ProviderSettings.QueryMap( + final Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( getContext().getContentResolver(), mProviderId, false /* keep updated */, null /* no handler */); @@ -70,7 +70,7 @@ public class ImRingtonePreference extends RingtonePreference { @Override protected void onSaveRingtone(Uri ringtoneUri) { - final Im.ProviderSettings.QueryMap settings = new Im.ProviderSettings.QueryMap( + final Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( getContext().getContentResolver(), mProviderId, false /* keep updated */, null /* no handler */); diff --git a/src/com/android/im/app/ImUrlActivity.java b/src/com/android/im/app/ImUrlActivity.java index a23a7fd..134a83f 100644 --- a/src/com/android/im/app/ImUrlActivity.java +++ b/src/com/android/im/app/ImUrlActivity.java @@ -20,6 +20,8 @@ import com.android.im.IChatSession; import com.android.im.IChatSessionManager; import com.android.im.IImConnection; import com.android.im.engine.ImConnection; +import com.android.im.provider.Imps; + import android.app.Activity; import android.content.ContentResolver; import android.content.ContentUris; @@ -29,7 +31,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; -import android.provider.Im; import android.text.TextUtils; import android.util.Log; @@ -38,8 +39,8 @@ import java.util.Set; public class ImUrlActivity extends Activity { private static final String[] ACCOUNT_PROJECTION = { - Im.Account._ID, - Im.Account.PASSWORD, + Imps.Account._ID, + Imps.Account.PASSWORD, }; private static final int ACCOUNT_ID_COLUMN = 0; private static final int ACCOUNT_PW_COLUMN = 1; @@ -78,7 +79,7 @@ public class ImUrlActivity extends Activity { void handleIntent() { ContentResolver cr = getContentResolver(); - long providerId = Im.Provider.getProviderIdForName(cr, mProviderName); + long providerId = Imps.Provider.getProviderIdForName(cr, mProviderName); long accountId; mConn= mApp.getConnection(providerId); @@ -119,13 +120,13 @@ public class ImUrlActivity extends Activity { private void addAccount(long providerId) { Intent intent = new Intent(this, AccountActivity.class); intent.setAction(Intent.ACTION_INSERT); - intent.setData(ContentUris.withAppendedId(Im.Provider.CONTENT_URI, providerId)); + intent.setData(ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId)); intent.putExtra(ImApp.EXTRA_INTENT_SEND_TO_USER, mToAddress); startActivity(intent); } private void editAccount(long accountId) { - Uri accountUri = ContentUris.withAppendedId(Im.Account.CONTENT_URI, accountId); + Uri accountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); Intent intent = new Intent(this, AccountActivity.class); intent.setAction(Intent.ACTION_EDIT); intent.setData(accountUri); @@ -134,7 +135,7 @@ public class ImUrlActivity extends Activity { } private void signInAccount(long accountId) { - Uri accountUri = ContentUris.withAppendedId(Im.Account.CONTENT_URI, accountId); + Uri accountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); Intent intent = new Intent(this, SigningInActivity.class); intent.setData(accountUri); intent.putExtra(ImApp.EXTRA_INTENT_SEND_TO_USER, mToAddress); @@ -143,7 +144,7 @@ public class ImUrlActivity extends Activity { private void showContactList(long accountId) { Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Im.Contacts.CONTENT_URI); + intent.setData(Imps.Contacts.CONTENT_URI); intent.addCategory(ImApp.IMPS_CATEGORY); intent.putExtra("accountId", accountId); @@ -158,7 +159,7 @@ public class ImUrlActivity extends Activity { session = manager.createChatSession(mToAddress); } - Uri data = ContentUris.withAppendedId(Im.Chats.CONTENT_URI, session.getId()); + Uri data = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, session.getId()); Intent i = new Intent(Intent.ACTION_VIEW, data); i.putExtra("from", mToAddress); i.putExtra("providerId", provider); @@ -225,11 +226,11 @@ public class ImUrlActivity extends Activity { private String getProviderNameForCategory(String providerCategory) { if (providerCategory != null) { if (providerCategory.equalsIgnoreCase("com.android.im.category.AIM")) { - return Im.ProviderNames.AIM; + return Imps.ProviderNames.AIM; } else if (providerCategory.equalsIgnoreCase("com.android.im.category.MSN")) { - return Im.ProviderNames.MSN; + return Imps.ProviderNames.MSN; } else if (providerCategory.equalsIgnoreCase("com.android.im.category.YAHOO")) { - return Im.ProviderNames.YAHOO; + return Imps.ProviderNames.YAHOO; } } @@ -241,16 +242,16 @@ public class ImUrlActivity extends Activity { return null; } - if (Im.ProviderNames.AIM.equalsIgnoreCase(provider)) { - return Im.ProviderNames.AIM; + if (Imps.ProviderNames.AIM.equalsIgnoreCase(provider)) { + return Imps.ProviderNames.AIM; } - if (Im.ProviderNames.MSN.equalsIgnoreCase(provider)) { - return Im.ProviderNames.MSN; + if (Imps.ProviderNames.MSN.equalsIgnoreCase(provider)) { + return Imps.ProviderNames.MSN; } - if (Im.ProviderNames.YAHOO.equalsIgnoreCase(provider)) { - return Im.ProviderNames.YAHOO; + if (Imps.ProviderNames.YAHOO.equalsIgnoreCase(provider)) { + return Imps.ProviderNames.YAHOO; } return null; diff --git a/src/com/android/im/app/LandingPage.java b/src/com/android/im/app/LandingPage.java new file mode 100644 index 0000000..3c9ac99 --- /dev/null +++ b/src/com/android/im/app/LandingPage.java @@ -0,0 +1,459 @@ +/* + * 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.im.app; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.ContentUris; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.os.RemoteException; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.ContextMenu.ContextMenuInfo; +import android.widget.AdapterView; +import android.widget.CursorAdapter; +import android.widget.ListView; + +import com.android.im.IImConnection; +import com.android.im.R; +import com.android.im.plugin.BrandingResourceIDs; +import com.android.im.provider.Imps; + +public class LandingPage extends ListActivity implements View.OnCreateContextMenuListener { + private static final String TAG = ImApp.LOG_TAG; + + private static final int ID_SIGN_IN = Menu.FIRST + 1; + private static final int ID_SIGN_OUT = Menu.FIRST + 2; + private static final int ID_EDIT_ACCOUNT = Menu.FIRST + 3; + private static final int ID_REMOVE_ACCOUNT = Menu.FIRST + 4; + private static final int ID_SIGN_OUT_ALL = Menu.FIRST + 5; + private static final int ID_ADD_ACCOUNT = Menu.FIRST + 6; + private static final int ID_VIEW_CONTACT_LIST = Menu.FIRST + 7; + private static final int ID_SETTINGS = Menu.FIRST + 8; + + private ProviderAdapter mAdapter; + private Cursor mProviderCursor; + private ImApp mApp; + private SimpleAlertHandler mHandler; + + private static final String[] PROVIDER_PROJECTION = { + Imps.Provider._ID, + Imps.Provider.NAME, + Imps.Provider.FULLNAME, + Imps.Provider.CATEGORY, + Imps.Provider.ACTIVE_ACCOUNT_ID, + Imps.Provider.ACTIVE_ACCOUNT_USERNAME, + Imps.Provider.ACTIVE_ACCOUNT_PW, + Imps.Provider.ACTIVE_ACCOUNT_LOCKED, + Imps.Provider.ACTIVE_ACCOUNT_KEEP_SIGNED_IN, + Imps.Provider.ACCOUNT_PRESENCE_STATUS, + Imps.Provider.ACCOUNT_CONNECTION_STATUS, + }; + + static final int PROVIDER_ID_COLUMN = 0; + static final int PROVIDER_NAME_COLUMN = 1; + static final int PROVIDER_FULLNAME_COLUMN = 2; + static final int PROVIDER_CATEGORY_COLUMN = 3; + static final int ACTIVE_ACCOUNT_ID_COLUMN = 4; + static final int ACTIVE_ACCOUNT_USERNAME_COLUMN = 5; + static final int ACTIVE_ACCOUNT_PW_COLUMN = 6; + static final int ACTIVE_ACCOUNT_LOCKED = 7; + static final int ACTIVE_ACCOUNT_KEEP_SIGNED_IN = 8; + static final int ACCOUNT_PRESENCE_STATUS = 9; + static final int ACCOUNT_CONNECTION_STATUS = 10; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + setTitle(R.string.landing_page_title); + + mApp = ImApp.getApplication(this); + mHandler = new MyHandler(this); + + ImPluginHelper.getInstance(this).loadAvaiablePlugins(); + + mProviderCursor = managedQuery(Imps.Provider.CONTENT_URI_WITH_ACCOUNT, + PROVIDER_PROJECTION, + Imps.Provider.CATEGORY + "=?" /* selection */, + new String[]{ ImApp.IMPS_CATEGORY } /* selection args */, + Imps.Provider.DEFAULT_SORT_ORDER); + mAdapter = new ProviderAdapter(this, mProviderCursor); + setListAdapter(mAdapter); + + registerForContextMenu(getListView()); + } + + + @Override + protected void onPause() { + mHandler.unregisterForBroadcastEvents(); + + super.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + + mHandler.registerForBroadcastEvents(); + } + + private void signIn(long accountId) { + if (accountId == 0) { + Log.w(TAG, "signIn: account id is 0, bail"); + return; + } + + boolean isAccountEditible = mProviderCursor.getInt(ACTIVE_ACCOUNT_LOCKED) == 0; + if (isAccountEditible && mProviderCursor.isNull(ACTIVE_ACCOUNT_PW_COLUMN)) { + // no password, edit the account + if (Log.isLoggable(TAG, Log.DEBUG)) log("no pw for account " + accountId); + Intent intent = getEditAccountIntent(); + startActivity(intent); + return; + } + + Intent intent = new Intent(this, SigningInActivity.class); + intent.setData(ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId)); + startActivity(intent); + } + + boolean isSigningIn(Cursor cursor) { + int connectionStatus = cursor.getInt(ACCOUNT_CONNECTION_STATUS); + return connectionStatus == Imps.ConnectionStatus.CONNECTING; + } + + private boolean isSignedIn(Cursor cursor) { + int connectionStatus = cursor.getInt(ACCOUNT_CONNECTION_STATUS); + return connectionStatus == Imps.ConnectionStatus.ONLINE; + } + + private boolean allAccountsSignedOut() { + if(!mProviderCursor.moveToFirst()) { + return false; + } + do { + if (isSignedIn(mProviderCursor)) { + return false; + } + } while (mProviderCursor.moveToNext()) ; + + return true; + } + + private void signoutAll() { + DialogInterface.OnClickListener confirmListener + = new DialogInterface.OnClickListener(){ + public void onClick(DialogInterface dialog, int whichButton) { + do { + long accountId = mProviderCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN); + signOut(accountId); + } while (mProviderCursor.moveToNext()) ; + } + }; + + new AlertDialog.Builder(this) + .setTitle(R.string.confirm) + .setMessage(R.string.signout_all_confirm_message) + .setPositiveButton(R.string.yes, confirmListener) // default button + .setNegativeButton(R.string.no, null) + .setCancelable(true) + .show(); + } + + private void signOut(long accountId) { + if (accountId == 0) { + Log.w(TAG, "signOut: account id is 0, bail"); + return; + } + + try { + IImConnection conn = mApp.getConnectionByAccount(accountId); + if (conn != null) { + conn.logout(); + } + } catch (RemoteException ex) { + Log.e(TAG, "signOut failed", ex); + } + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + menu.findItem(ID_SIGN_OUT_ALL).setVisible(!allAccountsSignedOut()); + return true; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, ID_SIGN_OUT_ALL, 0, R.string.menu_sign_out_all) + .setIcon(android.R.drawable.ic_menu_close_clear_cancel); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case ID_SIGN_OUT_ALL: + signoutAll(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + AdapterView.AdapterContextMenuInfo info; + try { + info = (AdapterView.AdapterContextMenuInfo) menuInfo; + } catch (ClassCastException e) { + Log.e(TAG, "bad menuInfo", e); + return; + } + + Cursor providerCursor = (Cursor) getListAdapter().getItem(info.position); + menu.setHeaderTitle(providerCursor.getString(PROVIDER_FULLNAME_COLUMN)); + + if (providerCursor.isNull(ACTIVE_ACCOUNT_ID_COLUMN)) { + menu.add(0, ID_ADD_ACCOUNT, 0, R.string.menu_add_account); + return; + } + + long providerId = providerCursor.getLong(PROVIDER_ID_COLUMN); + boolean isLoggingIn = isSigningIn(providerCursor); + boolean isLoggedIn = isSignedIn(providerCursor); + + BrandingResources brandingRes = mApp.getBrandingResource(providerId); + if (!isLoggedIn) { + menu.add(0, ID_SIGN_IN, 0, R.string.sign_in).setIcon(com.android.internal.R.drawable.ic_menu_login); + } else { + menu.add(0, ID_VIEW_CONTACT_LIST, 0, + brandingRes.getString(BrandingResourceIDs.STRING_MENU_CONTACT_LIST)); + menu.add(0, ID_SIGN_OUT, 0, R.string.menu_sign_out) + .setIcon(android.R.drawable.ic_menu_close_clear_cancel); + } + + boolean isAccountEditible = providerCursor.getInt(ACTIVE_ACCOUNT_LOCKED) == 0; + if (isAccountEditible && !isLoggingIn && !isLoggedIn) { + menu.add(0, ID_EDIT_ACCOUNT, 0, R.string.menu_edit_account) + .setIcon(android.R.drawable.ic_menu_edit); + menu.add(0, ID_REMOVE_ACCOUNT, 0, R.string.menu_remove_account) + .setIcon(android.R.drawable.ic_menu_delete); + } + + // always add a settings menu item + menu.add(0, ID_SETTINGS, 0, R.string.menu_settings); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + AdapterView.AdapterContextMenuInfo info; + try { + info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); + } catch (ClassCastException e) { + Log.e(TAG, "bad menuInfo", e); + return false; + } + long providerId = info.id; + Cursor providerCursor = (Cursor) getListAdapter().getItem(info.position); + long accountId = providerCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN); + + switch (item.getItemId()) { + case ID_EDIT_ACCOUNT: + { + startActivity(getEditAccountIntent()); + return true; + } + + case ID_REMOVE_ACCOUNT: + { + Uri accountUri = ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId); + getContentResolver().delete(accountUri, null, null); + // Requery the cursor to force refreshing screen + providerCursor.requery(); + return true; + } + + case ID_VIEW_CONTACT_LIST: + { + Intent intent = getViewContactsIntent(); + startActivity(intent); + return true; + } + case ID_ADD_ACCOUNT: + { + startActivity(getCreateAccountIntent()); + return true; + } + + case ID_SIGN_IN: + { + signIn(accountId); + return true; + } + + case ID_SIGN_OUT: + { + // TODO: progress bar + signOut(accountId); + return true; + } + + case ID_SETTINGS: + { + Intent intent = new Intent(Intent.ACTION_VIEW, Imps.ProviderSettings.CONTENT_URI); + intent.addCategory(getProviderCategory(providerCursor)); + intent.putExtra("providerId", providerId); + startActivity(intent); + return true; + } + + } + + return false; + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Intent intent = null; + mProviderCursor.moveToPosition(position); + + if (mProviderCursor.isNull(ACTIVE_ACCOUNT_ID_COLUMN)) { + // add account + intent = getCreateAccountIntent(); + } else { + int state = mProviderCursor.getInt(ACCOUNT_CONNECTION_STATUS); + long accountId = mProviderCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN); + + if (state == Imps.ConnectionStatus.OFFLINE) { + boolean isKeepSignedIn = mProviderCursor.getInt(ACTIVE_ACCOUNT_KEEP_SIGNED_IN) != 0; + boolean isAccountEditible = mProviderCursor.getInt(ACTIVE_ACCOUNT_LOCKED) == 0; + if (isKeepSignedIn) { + signIn(accountId); + } else if(isAccountEditible) { + intent = getEditAccountIntent(); + } + } else if (state == Imps.ConnectionStatus.CONNECTING) { + signIn(accountId); + } else { + intent = getViewContactsIntent(); + } + } + + if (intent != null) { + startActivity(intent); + } + } + + Intent getCreateAccountIntent() { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_INSERT); + + long providerId = mProviderCursor.getLong(PROVIDER_ID_COLUMN); + intent.setData(ContentUris.withAppendedId(Imps.Provider.CONTENT_URI, providerId)); + intent.addCategory(getProviderCategory(mProviderCursor)); + return intent; + } + + Intent getEditAccountIntent() { + Intent intent = new Intent(Intent.ACTION_EDIT, + ContentUris.withAppendedId(Imps.Account.CONTENT_URI, + mProviderCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN))); + intent.addCategory(getProviderCategory(mProviderCursor)); + return intent; + } + + Intent getViewContactsIntent() { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Imps.Contacts.CONTENT_URI); + intent.addCategory(getProviderCategory(mProviderCursor)); + intent.putExtra("accountId", mProviderCursor.getLong(ACTIVE_ACCOUNT_ID_COLUMN)); + return intent; + } + + private String getProviderCategory(Cursor cursor) { + return cursor.getString(PROVIDER_CATEGORY_COLUMN); + } + + static void log(String msg) { + Log.d(TAG, "[LandingPage]" + msg); + } + + private class ProviderListItemFactory implements LayoutInflater.Factory { + public View onCreateView(String name, Context context, AttributeSet attrs) { + if (name != null && name.equals(ProviderListItem.class.getName())) { + return new ProviderListItem(context, LandingPage.this); + } + return null; + } + } + + private final class ProviderAdapter extends CursorAdapter { + private LayoutInflater mInflater; + + public ProviderAdapter(Context context, Cursor c) { + super(context, c); + mInflater = LayoutInflater.from(context).cloneInContext(context); + mInflater.setFactory(new ProviderListItemFactory()); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + // create a custom view, so we can manage it ourselves. Mainly, we want to + // initialize the widget views (by calling getViewById()) in newView() instead of in + // bindView(), which can be called more often. + ProviderListItem view = (ProviderListItem) mInflater.inflate( + R.layout.account_view, parent, false); + view.init(cursor); + return view; + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + ((ProviderListItem) view).bindView(cursor); + } + } + + private final static class MyHandler extends SimpleAlertHandler { + + public MyHandler(Activity activity) { + super(activity); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == ImApp.EVENT_CONNECTION_DISCONNECTED) { + promptDisconnectedEvent(msg); + } + super.handleMessage(msg); + } + } +} diff --git a/src/com/android/im/app/MessageView.java b/src/com/android/im/app/MessageView.java index 9a42cbb..5e2424f 100644 --- a/src/com/android/im/app/MessageView.java +++ b/src/com/android/im/app/MessageView.java @@ -23,7 +23,6 @@ import java.util.Date; import android.content.Context; import android.content.res.Resources; import android.graphics.Typeface; -import android.provider.Im; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; @@ -36,6 +35,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.im.R; +import com.android.im.provider.Imps; public class MessageView extends LinearLayout { @@ -132,20 +132,20 @@ public class MessageView extends LinearLayout { boolean isGroupChat, boolean scrolling) { String body; switch (type) { - case Im.MessageType.PRESENCE_AVAILABLE: + case Imps.MessageType.PRESENCE_AVAILABLE: body = mResources.getString(isGroupChat ? R.string.contact_joined : R.string.contact_online, contact); break; - case Im.MessageType.PRESENCE_AWAY: + case Imps.MessageType.PRESENCE_AWAY: body = mResources.getString(R.string.contact_away, contact); break; - case Im.MessageType.PRESENCE_DND: + case Imps.MessageType.PRESENCE_DND: body = mResources.getString(R.string.contact_busy, contact); break; - case Im.MessageType.PRESENCE_UNAVAILABLE: + case Imps.MessageType.PRESENCE_UNAVAILABLE: body = mResources.getString(isGroupChat ? R.string.contact_left : R.string.contact_offline, contact); break; diff --git a/src/com/android/im/app/NewChatActivity.java b/src/com/android/im/app/NewChatActivity.java index 62f1790..d8c6c1d 100644 --- a/src/com/android/im/app/NewChatActivity.java +++ b/src/com/android/im/app/NewChatActivity.java @@ -26,6 +26,7 @@ import com.android.im.IChatSession; import com.android.im.R; import com.android.im.app.adapter.ChatListenerAdapter; import com.android.im.plugin.BrandingResourceIDs; +import com.android.im.provider.Imps; import android.app.Activity; import android.app.AlertDialog; @@ -39,7 +40,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; -import android.provider.Im; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; @@ -134,9 +134,9 @@ public class NewChatActivity extends Activity { } else { Uri data = intent.getData(); String type = getContentResolver().getType(data); - if (Im.Chats.CONTENT_ITEM_TYPE.equals(type)) { + if (Imps.Chats.CONTENT_ITEM_TYPE.equals(type)) { mChatView.bindChat(ContentUris.parseId(data)); - } else if (Im.Invitation.CONTENT_ITEM_TYPE.equals(type)) { + } else if (Imps.Invitation.CONTENT_ITEM_TYPE.equals(type)) { mChatView.bindInvitation(ContentUris.parseId(data)); } } @@ -173,8 +173,8 @@ public class NewChatActivity extends Activity { //XXX HACK: Yahoo! doesn't allow to block a friend. We can only block a temporary contact. ProviderDef provider = mApp.getProvider(mChatView.getProviderId()); - if ((provider != null) && Im.ProviderNames.YAHOO.equals(provider.mName)) { - if (Im.Contacts.TYPE_TEMPORARY != mChatView.mType) { + if ((provider != null) && Imps.ProviderNames.YAHOO.equals(provider.mName)) { + if (Imps.Contacts.TYPE_TEMPORARY != mChatView.mType) { menu.findItem(R.id.menu_block_contact).setVisible(false); } } @@ -347,7 +347,7 @@ public class NewChatActivity extends Activity { } private void startContactPicker() { - Uri.Builder builder = Im.Contacts.CONTENT_URI_ONLINE_CONTACTS_BY.buildUpon(); + Uri.Builder builder = Imps.Contacts.CONTENT_URI_ONLINE_CONTACTS_BY.buildUpon(); ContentUris.appendId(builder, mChatView.getProviderId()); ContentUris.appendId(builder, mChatView.getAccountId()); Uri data = builder.build(); diff --git a/src/com/android/im/app/PreferenceActivity.java b/src/com/android/im/app/PreferenceActivity.java index 99e3f6e..14215ec 100644 --- a/src/com/android/im/app/PreferenceActivity.java +++ b/src/com/android/im/app/PreferenceActivity.java @@ -24,7 +24,6 @@ import android.content.ContentValues; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; -import android.provider.Im; import android.util.Log; import android.view.View; import android.widget.Button; @@ -37,6 +36,7 @@ import com.android.im.imps.ImpsConnectionConfig.EncodingType; import com.android.im.imps.ImpsConnectionConfig.TransportType; import com.android.im.plugin.ImConfigNames; import com.android.im.plugin.ImpsConfigNames; +import com.android.im.provider.Imps; public class PreferenceActivity extends Activity { @@ -121,7 +121,7 @@ public class PreferenceActivity extends Activity { finish(); } else { Cursor c = getContentResolver().query(i.getData(), - new String[]{Im.Provider._ID, Im.Provider.NAME}, null, null, null); + new String[]{Imps.Provider._ID, Imps.Provider.NAME}, null, null, null); if (c == null || !c.moveToFirst()) { Log.w(ImApp.LOG_TAG, "Can't query data from given URI."); finish(); @@ -131,7 +131,7 @@ public class PreferenceActivity extends Activity { c.close(); - mPref = Im.ProviderSettings.queryProviderSettings(getContentResolver(), mProviderId); + mPref = Imps.ProviderSettings.queryProviderSettings(getContentResolver(), mProviderId); } } } @@ -203,16 +203,16 @@ public class PreferenceActivity extends Activity { valuesList[4] = getValues(ImpsConfigNames.HOST, host); valuesList[6] = getValues(ImpsConfigNames.MSISDN, msisdn); - getContentResolver().bulkInsert(Im.ProviderSettings.CONTENT_URI, valuesList); + getContentResolver().bulkInsert(Imps.ProviderSettings.CONTENT_URI, valuesList); finish(); } private ContentValues getValues(String name, String value) { ContentValues values = new ContentValues(); - values.put(Im.ProviderSettings.PROVIDER, mProviderId); - values.put(Im.ProviderSettings.NAME, name); - values.put(Im.ProviderSettings.VALUE, value); + values.put(Imps.ProviderSettings.PROVIDER, mProviderId); + values.put(Imps.ProviderSettings.NAME, name); + values.put(Imps.ProviderSettings.VALUE, value); return values; } diff --git a/src/com/android/im/app/PresenceUtils.java b/src/com/android/im/app/PresenceUtils.java index dcc806c..49861e6 100644 --- a/src/com/android/im/app/PresenceUtils.java +++ b/src/com/android/im/app/PresenceUtils.java @@ -16,11 +16,11 @@ */ package com.android.im.app; -import android.provider.Im; import android.util.Log; import com.android.im.engine.Presence; import com.android.im.plugin.BrandingResourceIDs; +import com.android.im.provider.Imps; public final class PresenceUtils { private PresenceUtils() {} @@ -28,44 +28,44 @@ public final class PresenceUtils { public static int convertStatus(int status) { switch (status) { case Presence.AVAILABLE: - return Im.Presence.AVAILABLE; + return Imps.Presence.AVAILABLE; case Presence.AWAY: - return Im.Presence.AWAY; + return Imps.Presence.AWAY; case Presence.DO_NOT_DISTURB: - return Im.Presence.DO_NOT_DISTURB; + return Imps.Presence.DO_NOT_DISTURB; case Presence.IDLE: - return Im.Presence.IDLE; + return Imps.Presence.IDLE; case Presence.OFFLINE: - return Im.Presence.OFFLINE; + return Imps.Presence.OFFLINE; default: Log.w(ImApp.LOG_TAG, "[ContactView] Unknown presence status " + status); - return Im.Presence.AVAILABLE; + return Imps.Presence.AVAILABLE; } } public static int getStatusStringRes(int status) { switch (status) { - case Im.Presence.AVAILABLE: + case Imps.Presence.AVAILABLE: return BrandingResourceIDs.STRING_PRESENCE_AVAILABLE; - case Im.Presence.AWAY: + case Imps.Presence.AWAY: return BrandingResourceIDs.STRING_PRESENCE_AWAY; - case Im.Presence.DO_NOT_DISTURB: + case Imps.Presence.DO_NOT_DISTURB: return BrandingResourceIDs.STRING_PRESENCE_BUSY; - case Im.Presence.IDLE: + case Imps.Presence.IDLE: return BrandingResourceIDs.STRING_PRESENCE_IDLE; - case Im.Presence.INVISIBLE: + case Imps.Presence.INVISIBLE: return BrandingResourceIDs.STRING_PRESENCE_INVISIBLE; - case Im.Presence.OFFLINE: + case Imps.Presence.OFFLINE: return BrandingResourceIDs.STRING_PRESENCE_OFFLINE; default: @@ -75,19 +75,19 @@ public final class PresenceUtils { public static int getStatusIconId(int status) { switch (status) { - case Im.Presence.AVAILABLE: + case Imps.Presence.AVAILABLE: return BrandingResourceIDs.DRAWABLE_PRESENCE_ONLINE; - case Im.Presence.IDLE: + case Imps.Presence.IDLE: return BrandingResourceIDs.DRAWABLE_PRESENCE_AWAY; - case Im.Presence.AWAY: + case Imps.Presence.AWAY: return BrandingResourceIDs.DRAWABLE_PRESENCE_AWAY; - case Im.Presence.DO_NOT_DISTURB: + case Imps.Presence.DO_NOT_DISTURB: return BrandingResourceIDs.DRAWABLE_PRESENCE_BUSY; - case Im.Presence.INVISIBLE: + case Imps.Presence.INVISIBLE: return BrandingResourceIDs.DRAWABLE_PRESENCE_INVISIBLE; default: diff --git a/src/com/android/im/app/ProviderListItem.java b/src/com/android/im/app/ProviderListItem.java new file mode 100644 index 0000000..00679d4 --- /dev/null +++ b/src/com/android/im/app/ProviderListItem.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2009 Myriad Group AG + * 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. + */ + +package com.android.im.app; + +import com.android.im.R; +import com.android.im.plugin.BrandingResourceIDs; +import com.android.im.provider.Imps; + +import android.graphics.drawable.Drawable; +import android.widget.LinearLayout; +import android.widget.ImageView; +import android.widget.TextView; +import android.content.Context; +import android.content.ContentResolver; +import android.content.res.Resources; +import android.database.Cursor; +import android.content.res.ColorStateList; +import android.view.View; +import android.util.Log; + +public class ProviderListItem extends LinearLayout { + private static final String TAG = "IM"; + private static final boolean LOCAL_DEBUG = false; + + private LandingPage mActivity; + private ImageView mProviderIcon; + private ImageView mStatusIcon; + private TextView mProviderName; + private TextView mLoginName; + private TextView mChatView; + private View mUnderBubble; + private Drawable mBubbleDrawable, mDefaultBackground; + + private int mProviderIdColumn; + private int mProviderFullnameColumn; + private int mActiveAccountIdColumn; + private int mActiveAccountUserNameColumn; + private int mAccountPresenceStatusColumn; + private int mAccountConnectionStatusColumn; + + private ColorStateList mProviderNameColors; + private ColorStateList mLoginNameColors; + private ColorStateList mChatViewColors; + + public ProviderListItem(Context context, LandingPage activity) { + super(context); + mActivity = activity; + } + + public void init(Cursor c) { + mProviderIcon = (ImageView) findViewById(R.id.providerIcon); + mStatusIcon = (ImageView) findViewById(R.id.statusIcon); + mProviderName = (TextView) findViewById(R.id.providerName); + mLoginName = (TextView) findViewById(R.id.loginName); + mChatView = (TextView) findViewById(R.id.conversations); + mUnderBubble = findViewById(R.id.underBubble); + mBubbleDrawable = getResources().getDrawable(R.drawable.bubble); + mDefaultBackground = getResources().getDrawable(R.drawable.default_background); + + mProviderIdColumn = c.getColumnIndexOrThrow(Imps.Provider._ID); + mProviderFullnameColumn = c.getColumnIndexOrThrow(Imps.Provider.FULLNAME); + mActiveAccountIdColumn = c.getColumnIndexOrThrow( + Imps.Provider.ACTIVE_ACCOUNT_ID); + mActiveAccountUserNameColumn = c.getColumnIndexOrThrow( + Imps.Provider.ACTIVE_ACCOUNT_USERNAME); + mAccountPresenceStatusColumn = c.getColumnIndexOrThrow( + Imps.Provider.ACCOUNT_PRESENCE_STATUS); + mAccountConnectionStatusColumn = c.getColumnIndexOrThrow( + Imps.Provider.ACCOUNT_CONNECTION_STATUS); + + mProviderNameColors = mProviderName.getTextColors(); + mLoginNameColors = mLoginName.getTextColors(); + mChatViewColors = mChatView.getTextColors(); + } + + public void bindView(Cursor cursor) { + Resources r = getResources(); + ImageView providerIcon = mProviderIcon; + ImageView statusIcon = mStatusIcon; + TextView providerName = mProviderName; + TextView loginName = mLoginName; + TextView chatView = mChatView; + + int providerId = cursor.getInt(mProviderIdColumn); + String providerDisplayName = cursor.getString(mProviderFullnameColumn); + + ImApp app = ImApp.getApplication(mActivity); + BrandingResources brandingRes = app.getBrandingResource(providerId); + providerIcon.setImageDrawable( + brandingRes.getDrawable(BrandingResourceIDs.DRAWABLE_LOGO)); + + mUnderBubble.setBackgroundDrawable(mDefaultBackground); + statusIcon.setVisibility(View.GONE); + + providerName.setTextColor(mProviderNameColors); + loginName.setTextColor(mLoginNameColors); + chatView.setTextColor(mChatViewColors); + + if (!cursor.isNull(mActiveAccountIdColumn)) { + mLoginName.setVisibility(View.VISIBLE); + providerName.setVisibility(View.VISIBLE); + providerName.setText(providerDisplayName); + + long accountId = cursor.getLong(mActiveAccountIdColumn); + int connectionStatus = cursor.getInt(mAccountConnectionStatusColumn); + + String secondRowText; + + chatView.setVisibility(View.GONE); + + switch (connectionStatus) { + case Imps.ConnectionStatus.CONNECTING: + secondRowText = r.getString(R.string.signing_in_wait); + break; + + case Imps.ConnectionStatus.ONLINE: + int presenceIconId = getPresenceIconId(cursor); + statusIcon.setImageDrawable( + brandingRes.getDrawable(presenceIconId)); + statusIcon.setVisibility(View.VISIBLE); + ContentResolver cr = mActivity.getContentResolver(); + + int count = getConversationCount(cr, accountId); + if (count > 0) { + mUnderBubble.setBackgroundDrawable(mBubbleDrawable); + chatView.setVisibility(View.VISIBLE); + chatView.setText(r.getString(R.string.conversations, count)); + + providerName.setTextColor(0xff000000); + loginName.setTextColor(0xff000000); + chatView.setTextColor(0xff000000); + } else { + chatView.setVisibility(View.GONE); + } + + secondRowText = cursor.getString(mActiveAccountUserNameColumn); + break; + + default: + secondRowText = cursor.getString(mActiveAccountUserNameColumn); + break; + } + + loginName.setText(secondRowText); + + } else { + // No active account, show add account + mLoginName.setVisibility(View.GONE); + mChatView.setVisibility(View.GONE); + mProviderName.setText(providerDisplayName); + } + } + + private int getConversationCount(ContentResolver cr, long accountId) { + // TODO: this is code used to get Google Talk's chat count. Not sure if this will work + // for IMPS chat count. + StringBuilder where = new StringBuilder(); + where.append(Imps.Chats.CONTACT_ID); + where.append(" in (select _id from contacts where "); + where.append(Imps.Contacts.ACCOUNT); + where.append("="); + where.append(accountId); + where.append(")"); + + Cursor cursor = cr.query(Imps.Chats.CONTENT_URI, null, where.toString(), null, null); + + try { + return cursor.getCount(); + } finally { + cursor.close(); + } + } + + private int getPresenceIconId(Cursor cursor) { + int presenceStatus = cursor.getInt(mAccountPresenceStatusColumn); + + if (LOCAL_DEBUG) log("getPresenceIconId: presenceStatus=" + presenceStatus); + + switch (presenceStatus) { + case Imps.Presence.AVAILABLE: + return BrandingResourceIDs.DRAWABLE_PRESENCE_ONLINE; + + case Imps.Presence.IDLE: + case Imps.Presence.AWAY: + return BrandingResourceIDs.DRAWABLE_PRESENCE_AWAY; + + case Imps.Presence.DO_NOT_DISTURB: + return BrandingResourceIDs.DRAWABLE_PRESENCE_BUSY; + + case Imps.Presence.INVISIBLE: + return BrandingResourceIDs.DRAWABLE_PRESENCE_INVISIBLE; + + default: + return BrandingResourceIDs.DRAWABLE_PRESENCE_OFFLINE; + } + } + + private void log(String msg) { + Log.d(TAG, msg); + } +} diff --git a/src/com/android/im/app/SettingActivity.java b/src/com/android/im/app/SettingActivity.java index 1a5f73b..5e16c36 100644 --- a/src/com/android/im/app/SettingActivity.java +++ b/src/com/android/im/app/SettingActivity.java @@ -22,10 +22,10 @@ import android.os.Bundle; import android.preference.Preference; import android.preference.PreferenceScreen; import android.preference.CheckBoxPreference; -import android.provider.Im; import android.util.Log; import com.android.im.R; +import com.android.im.provider.Imps; import com.android.im.service.ImServiceConstants; public class SettingActivity extends android.preference.PreferenceActivity { @@ -50,7 +50,7 @@ public class SettingActivity extends android.preference.PreferenceActivity { } private void setInitialValues() { - Im.ProviderSettings.QueryMap settings = new Im.ProviderSettings.QueryMap( + Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( getContentResolver(), mProviderId, false /* keep updated */, null /* no handler */); @@ -71,7 +71,7 @@ public class SettingActivity extends android.preference.PreferenceActivity { @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { if (preference instanceof CheckBoxPreference) { - final Im.ProviderSettings.QueryMap settings = new Im.ProviderSettings.QueryMap( + final Imps.ProviderSettings.QueryMap settings = new Imps.ProviderSettings.QueryMap( getContentResolver(), mProviderId, false /* keep updated */, null /* no handler */); String key = preference.getKey(); diff --git a/src/com/android/im/app/SigningInActivity.java b/src/com/android/im/app/SigningInActivity.java index bcd7825..1e42645 100644 --- a/src/com/android/im/app/SigningInActivity.java +++ b/src/com/android/im/app/SigningInActivity.java @@ -25,6 +25,7 @@ import com.android.im.app.adapter.ConnectionListenerAdapter; import com.android.im.engine.ImConnection; import com.android.im.engine.ImErrorInfo; import com.android.im.plugin.BrandingResourceIDs; +import com.android.im.provider.Imps; import com.android.im.service.ImServiceConstants; import android.app.Activity; @@ -40,7 +41,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; import android.os.Handler; -import android.provider.Im; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -102,13 +102,13 @@ public class SigningInActivity extends Activity { return; } - mProviderId = c.getLong(c.getColumnIndexOrThrow(Im.Account.PROVIDER)); - mAccountId = c.getLong(c.getColumnIndexOrThrow(Im.Account._ID)); - mUserName = c.getString(c.getColumnIndexOrThrow(Im.Account.USERNAME)); + mProviderId = c.getLong(c.getColumnIndexOrThrow(Imps.Account.PROVIDER)); + mAccountId = c.getLong(c.getColumnIndexOrThrow(Imps.Account._ID)); + mUserName = c.getString(c.getColumnIndexOrThrow(Imps.Account.USERNAME)); String pwExtra = intent.getStringExtra(ImApp.EXTRA_INTENT_PASSWORD); mPassword = pwExtra != null ? pwExtra - : c.getString(c.getColumnIndexOrThrow(Im.Account.PASSWORD)); - final boolean isActive = c.getInt(c.getColumnIndexOrThrow(Im.Account.ACTIVE)) == 1; + : c.getString(c.getColumnIndexOrThrow(Imps.Account.PASSWORD)); + final boolean isActive = c.getInt(c.getColumnIndexOrThrow(Imps.Account.ACTIVE)) == 1; c.close(); mApp = ImApp.getApplication(this); @@ -196,13 +196,13 @@ public class SigningInActivity extends Activity { // this provider to inactive first and then update this // account to active. ContentValues values = new ContentValues(1); - values.put(Im.Account.ACTIVE, 0); + values.put(Imps.Account.ACTIVE, 0); ContentResolver cr = getContentResolver(); - cr.update(Im.Account.CONTENT_URI, values, - Im.Account.PROVIDER + "=" + providerId, null); + cr.update(Imps.Account.CONTENT_URI, values, + Imps.Account.PROVIDER + "=" + providerId, null); - values.put(Im.Account.ACTIVE, 1); - cr.update(ContentUris.withAppendedId(Im.Account.CONTENT_URI, accountId), + values.put(Imps.Account.ACTIVE, 1); + cr.update(ContentUris.withAppendedId(Imps.Account.CONTENT_URI, accountId), values, null, null); } @@ -307,7 +307,7 @@ public class SigningInActivity extends Activity { if(session == null) { session = manager.createChatSession(mToAddress); } - Uri data = ContentUris.withAppendedId(Im.Chats.CONTENT_URI, session.getId()); + Uri data = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, session.getId()); intent = new Intent(Intent.ACTION_VIEW, data); intent.putExtra("from", mToAddress); intent.putExtra("providerId", mProviderId); diff --git a/src/com/android/im/app/SignoutActivity.java b/src/com/android/im/app/SignoutActivity.java index 2cb3bcb..0b70b69 100644 --- a/src/com/android/im/app/SignoutActivity.java +++ b/src/com/android/im/app/SignoutActivity.java @@ -17,7 +17,6 @@ package com.android.im.app; import android.app.Activity; -import android.provider.Im; import android.os.Handler; import android.os.Bundle; import android.os.RemoteException; @@ -28,13 +27,14 @@ import android.net.Uri; import android.util.Log; import android.database.Cursor; import com.android.im.IImConnection; +import com.android.im.provider.Imps; public class SignoutActivity extends Activity { private String[] ACCOUNT_SELECTION = new String[] { - Im.Account._ID, - Im.Account.PROVIDER, + Imps.Account._ID, + Imps.Account.PROVIDER, }; private ImApp mApp; @@ -69,8 +69,8 @@ public class SignoutActivity extends Activity { return; } - providerId = c.getLong(c.getColumnIndexOrThrow(Im.Account.PROVIDER)); - accountId = c.getLong(c.getColumnIndexOrThrow(Im.Account._ID)); + providerId = c.getLong(c.getColumnIndexOrThrow(Imps.Account.PROVIDER)); + accountId = c.getLong(c.getColumnIndexOrThrow(Imps.Account._ID)); } finally { c.close(); } @@ -94,12 +94,12 @@ public class SignoutActivity extends Activity { // status will never be updated. Clear the status in this case // to make it recoverable from the crash. ContentValues values = new ContentValues(2); - values.put(Im.AccountStatus.PRESENCE_STATUS, - Im.Presence.OFFLINE); - values.put(Im.AccountStatus.CONNECTION_STATUS, - Im.ConnectionStatus.OFFLINE); - String where = Im.AccountStatus.ACCOUNT + "=?"; - getContentResolver().update(Im.AccountStatus.CONTENT_URI, + values.put(Imps.AccountStatus.PRESENCE_STATUS, + Imps.Presence.OFFLINE); + values.put(Imps.AccountStatus.CONNECTION_STATUS, + Imps.ConnectionStatus.OFFLINE); + String where = Imps.AccountStatus.ACCOUNT + "=?"; + getContentResolver().update(Imps.AccountStatus.CONTENT_URI, values, where, new String[] { Long.toString(accountId) }); } diff --git a/src/com/android/im/app/UserPresenceView.java b/src/com/android/im/app/UserPresenceView.java index 1250600..5e0e7ee 100644 --- a/src/com/android/im/app/UserPresenceView.java +++ b/src/com/android/im/app/UserPresenceView.java @@ -21,6 +21,7 @@ import com.android.im.R; import com.android.im.engine.ImErrorInfo; import com.android.im.engine.Presence; import com.android.im.plugin.ImpsConfigNames; +import com.android.im.provider.Imps; import com.google.android.collect.Lists; import android.app.Activity; @@ -29,7 +30,6 @@ import android.content.Context; import android.content.DialogInterface; import android.graphics.drawable.Drawable; import android.os.RemoteException; -import android.provider.Im; import android.text.TextUtils; import android.util.AttributeSet; import android.view.KeyEvent; @@ -100,8 +100,8 @@ public class UserPresenceView extends LinearLayout { int[] supportedStatus = mConn.getSupportedPresenceStatus(); for (int i = 0; i < supportedStatus.length; i++) { int s = PresenceUtils.convertStatus(supportedStatus[i]); - if (s == Im.Presence.OFFLINE) { - s = Im.Presence.INVISIBLE; + if (s == Imps.Presence.OFFLINE) { + s = Imps.Presence.INVISIBLE; } ImApp app = ImApp.getApplication((Activity)mContext); BrandingResources brandingRes = app.getBrandingResource(mProviderId); @@ -162,14 +162,14 @@ public class UserPresenceView extends LinearLayout { // the AIM and MSN server don't support it now. ProviderDef provider = app.getProvider(mProviderId); String providerName = provider == null ? null : provider.mName; - if (Im.ProviderNames.AIM.equals(providerName) - || Im.ProviderNames.MSN.equals(providerName)) { + if (Imps.ProviderNames.AIM.equals(providerName) + || Imps.ProviderNames.MSN.equals(providerName)) { mStatusBar.setFocusable(false); } } private TextView initStatusBar(long providerId) { - String value = Im.ProviderSettings.getStringValue( + String value = Imps.ProviderSettings.getStringValue( mContext.getContentResolver(), providerId, ImpsConfigNames.SUPPORT_USER_DEFINED_PRESENCE); diff --git a/src/com/android/im/engine/SmsService.java b/src/com/android/im/engine/SmsService.java index 5ba170b..a5b0a46 100644 --- a/src/com/android/im/engine/SmsService.java +++ b/src/com/android/im/engine/SmsService.java @@ -20,6 +20,8 @@ package com.android.im.engine; * An abstract interface to access system SMS service. */ public interface SmsService { + public static final String ANY_ADDRESS = "*"; + /** * The listener which will be notified when an incoming SMS is received. * diff --git a/src/com/android/im/imps/CirChannel.java b/src/com/android/im/imps/CirChannel.java index f147d00..48228c8 100644 --- a/src/com/android/im/imps/CirChannel.java +++ b/src/com/android/im/imps/CirChannel.java @@ -41,9 +41,17 @@ abstract class CirChannel { */ public abstract void connect() throws ImException; + /** + * Re-establish the connection and drop the old one. + */ public void reconnect(){ } + /** + * Tells if the CIR has been shutdown or not. + */ + public abstract boolean isShutdown(); + /** * Shutdown the CIR channel, stops to listen to CIR requests from the server. * diff --git a/src/com/android/im/imps/CustomPasswordDigest.java b/src/com/android/im/imps/CustomPasswordDigest.java index 5ce1f5a..c2c758d 100644 --- a/src/com/android/im/imps/CustomPasswordDigest.java +++ b/src/com/android/im/imps/CustomPasswordDigest.java @@ -17,21 +17,19 @@ package com.android.im.imps; import com.android.im.engine.ImException; -import com.android.im.plugin.IPasswordDigest; - -import android.os.RemoteException; +import com.android.im.plugin.PasswordDigest; import dalvik.system.PathClassLoader; public class CustomPasswordDigest implements PasswordDigest { - private IPasswordDigest mPasswordDigest; + private PasswordDigest mPasswordDigest; public CustomPasswordDigest(String pluginPath, String implClass) throws ImException { PathClassLoader classLoader = new PathClassLoader(pluginPath, getClass().getClassLoader()); try { - Class cls = classLoader.loadClass(implClass); - mPasswordDigest = (IPasswordDigest)cls.newInstance(); + Class cls = classLoader.loadClass(implClass); + mPasswordDigest = (PasswordDigest)cls.newInstance(); } catch (ClassNotFoundException e) { throw new ImException(e); } catch (IllegalAccessException e) { @@ -41,19 +39,11 @@ public class CustomPasswordDigest implements PasswordDigest { } } public String digest(String schema, String nonce, String password) throws ImException { - try { - return mPasswordDigest.digest(schema, nonce, password); - } catch (RemoteException e) { - throw new ImException(e); - } + return mPasswordDigest.digest(schema, nonce, password); } public String[] getSupportedDigestSchema() { - try { - return mPasswordDigest.getSupportedDigestSchema(); - } catch (RemoteException e) { - return new String[0]; - } + return mPasswordDigest.getSupportedDigestSchema(); } } diff --git a/src/com/android/im/imps/CustomPresenceMapping.java b/src/com/android/im/imps/CustomPresenceMapping.java index 9f7270e..e9f0176 100644 --- a/src/com/android/im/imps/CustomPresenceMapping.java +++ b/src/com/android/im/imps/CustomPresenceMapping.java @@ -16,25 +16,25 @@ */ package com.android.im.imps; +import java.util.Map; + +import android.os.RemoteException; + import com.android.im.engine.ImException; -import com.android.im.plugin.IPresenceMapping; import com.android.im.plugin.ImPluginConstants; +import com.android.im.plugin.PresenceMapping; import dalvik.system.PathClassLoader; -import android.os.RemoteException; - -import java.util.Map; - public class CustomPresenceMapping implements PresenceMapping { - private IPresenceMapping mPresenceMapping; + private PresenceMapping mPresenceMapping; public CustomPresenceMapping(String pluginPath, String implClass) throws ImException { PathClassLoader classLoader = new PathClassLoader(pluginPath, getClass().getClassLoader()); try { - Class cls = classLoader.loadClass(implClass); - mPresenceMapping = (IPresenceMapping)cls.newInstance(); + Class cls = classLoader.loadClass(implClass); + mPresenceMapping = (PresenceMapping)cls.newInstance(); } catch (ClassNotFoundException e) { throw new ImException(e); } catch (IllegalAccessException e) { @@ -45,52 +45,28 @@ public class CustomPresenceMapping implements PresenceMapping { } public Map getExtra(int status) { - try { - return mPresenceMapping.getExtra(status); - } catch (RemoteException e) { - return null; - } + return mPresenceMapping.getExtra(status); } public boolean getOnlineStatus(int status) { - try { - return mPresenceMapping.getOnlineStatus(status); - } catch (RemoteException e) { - return false; - } + return mPresenceMapping.getOnlineStatus(status); } public int getPresenceStatus(boolean onlineStatus, String userAvailability, Map allValues) { - try { - return mPresenceMapping.getPresenceStatus(onlineStatus, userAvailability, allValues); - } catch (RemoteException e) { - return ImPluginConstants.PRESENCE_OFFLINE; - } + return mPresenceMapping.getPresenceStatus(onlineStatus, userAvailability, allValues); } public int[] getSupportedPresenceStatus() { - try { - return mPresenceMapping.getSupportedPresenceStatus(); - } catch (RemoteException e) { - return new int[0]; - } + return mPresenceMapping.getSupportedPresenceStatus(); } public String getUserAvaibility(int status) { - try { - return mPresenceMapping.getUserAvaibility(status); - } catch (RemoteException e) { - return ImPluginConstants.PA_NOT_AVAILABLE; - } + return mPresenceMapping.getUserAvaibility(status); } public boolean requireAllPresenceValues() { - try { - return mPresenceMapping.requireAllPresenceValues(); - } catch (RemoteException e) { - return false; - } + return mPresenceMapping.requireAllPresenceValues(); } } diff --git a/src/com/android/im/imps/DefaultPresenceMapping.java b/src/com/android/im/imps/DefaultPresenceMapping.java index fa5f954..bf4e4fa 100644 --- a/src/com/android/im/imps/DefaultPresenceMapping.java +++ b/src/com/android/im/imps/DefaultPresenceMapping.java @@ -17,6 +17,7 @@ package com.android.im.imps; import com.android.im.plugin.ImPluginConstants; +import com.android.im.plugin.PresenceMapping; import java.util.Map; diff --git a/src/com/android/im/imps/HttpCirChannel.java b/src/com/android/im/imps/HttpCirChannel.java index 1cca355..77d6bf5 100644 --- a/src/com/android/im/imps/HttpCirChannel.java +++ b/src/com/android/im/imps/HttpCirChannel.java @@ -45,7 +45,7 @@ class HttpCirChannel extends CirChannel implements Runnable { } @Override - public void connect() { + public synchronized void connect() { ImpsSession session = mConnection.getSession(); try { if (session.getCirHttpAddress() != null) { @@ -56,13 +56,18 @@ class HttpCirChannel extends CirChannel implements Runnable { } mServerPollMin = session.getServerPollMin() * 1000; + mStopped = false; mPollingTask = new Thread(this, "HTTPCIRChannel"); mPollingTask.setDaemon(true); mPollingTask.start(); } + public synchronized boolean isShutdown() { + return mStopped; + } + @Override - public void shutdown() { + public synchronized void shutdown() { mStopped = true; } diff --git a/src/com/android/im/imps/ImpsConnectionConfig.java b/src/com/android/im/imps/ImpsConnectionConfig.java index 0986dbc..0d49a60 100644 --- a/src/com/android/im/imps/ImpsConnectionConfig.java +++ b/src/com/android/im/imps/ImpsConnectionConfig.java @@ -23,6 +23,8 @@ import com.android.im.engine.ConnectionConfig; import com.android.im.engine.ImException; import com.android.im.imps.ImpsConstants.ImpsVersion; import com.android.im.plugin.ImpsConfigNames; +import com.android.im.plugin.PasswordDigest; +import com.android.im.plugin.PresenceMapping; /** * The configuration for IMPS connection. diff --git a/src/com/android/im/imps/ImpsContactListManager.java b/src/com/android/im/imps/ImpsContactListManager.java index 480671d..31ddce2 100644 --- a/src/com/android/im/imps/ImpsContactListManager.java +++ b/src/com/android/im/imps/ImpsContactListManager.java @@ -32,6 +32,7 @@ import com.android.im.engine.ImException; import com.android.im.engine.Presence; import com.android.im.engine.SubscriptionRequestListener; import com.android.im.imps.ImpsConstants.ImpsVersion; +import com.android.im.plugin.PresenceMapping; /** * An implementation of ContactListManager of Wireless Village IMPS protocol. diff --git a/src/com/android/im/imps/ImpsPresenceUtils.java b/src/com/android/im/imps/ImpsPresenceUtils.java index ec1403f..e9b0b5f 100644 --- a/src/com/android/im/imps/ImpsPresenceUtils.java +++ b/src/com/android/im/imps/ImpsPresenceUtils.java @@ -18,6 +18,8 @@ package com.android.im.imps; import com.android.im.engine.Presence; +import com.android.im.plugin.PresenceMapping; + import org.apache.commons.codec.binary.Base64; import android.os.Base64Utils; @@ -151,7 +153,7 @@ public class ImpsPresenceUtils { if (value instanceof String) { elem.setContents((String)value); } else if (value instanceof Map) { - mapToPrimitives((Map)value, elem.getChildren()); + mapToPrimitives((Map)value, elem.getChildren()); } elems.add(elem); } diff --git a/src/com/android/im/imps/PasswordDigest.java b/src/com/android/im/imps/PasswordDigest.java deleted file mode 100644 index d911fc5..0000000 --- a/src/com/android/im/imps/PasswordDigest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.im.imps; - -import com.android.im.engine.ImException; - -public interface PasswordDigest { - /** - * Gets an array of supported digest schema. - * - * @return an array of digest schema - */ - String[] getSupportedDigestSchema(); - - /** - * Generates digest bytes. - * - */ - String digest(String schema, String nonce, String password) throws ImException; -} diff --git a/src/com/android/im/imps/PresenceMapping.java b/src/com/android/im/imps/PresenceMapping.java deleted file mode 100644 index 04602a9..0000000 --- a/src/com/android/im/imps/PresenceMapping.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.im.imps; - -import java.util.Map; - -public interface PresenceMapping { - /** - * Tells if the mapping needs all presence values sent in protocol. If this - * method returns true, the framework will pass all the presence values - * received from the server when map to the predefined status. - * - * @return true if needs; false otherwise. - */ - boolean requireAllPresenceValues(); - - /** - * Map the presence values sent in protocol to the predefined presence - * status. - * - * @param onlineStatus The value of presence <OnlineStatus> received - * from the server. - * @param userAvailability The value of presence <UserAvailibility> - * received from the server. - * @param allValues The whole presence values received from the server. - * @return a predefined status. - * @see #requireAllPresenceValues() - */ - int getPresenceStatus(boolean onlineStatus, String userAvailability, - Map allValues); - - /** - * Gets the value of <OnlineStatus> will be sent to the server when - * update presence to the predefined status. - * - * @param status the predefined status. - * @return The value of <OnlineStatus> will be sent to the server - */ - boolean getOnlineStatus(int status); - - /** - * Gets the value of <UserAvaibility> will be sent to the server when - * update presence to the predefined status. - * - * @param status the predefined status. - * @return The value of <UserAvaibility> will be sent to the server - */ - String getUserAvaibility(int status); - - /** - * Gets the extra presence values other than <OnlineStatus> and - * <UserAvaibility> will be sent to the server when update presence to - * the predefined status. - * - * @param status the predefined status. - * @return The extra values that will be sent to the server. - */ - Map getExtra(int status); - - /** - * Gets an array of the supported presence status. The client can only update - * presence to the values in the array. - * - * @return an array of the supported presence status. - */ - int[] getSupportedPresenceStatus(); -} diff --git a/src/com/android/im/imps/PresencePollingManager.java b/src/com/android/im/imps/PresencePollingManager.java index e7d917c..b3601db 100644 --- a/src/com/android/im/imps/PresencePollingManager.java +++ b/src/com/android/im/imps/PresencePollingManager.java @@ -86,7 +86,9 @@ public class PresencePollingManager implements Runnable { // poll. Fetch the presence of all contacts in list. pollingAddress = getContactLists(); } - mManager.fetchPresence(pollingAddress); + if (pollingAddress != null) { + mManager.fetchPresence(pollingAddress); + } } try { diff --git a/src/com/android/im/imps/SmsCirChannel.java b/src/com/android/im/imps/SmsCirChannel.java index 24ece3b..12f9f1d 100644 --- a/src/com/android/im/imps/SmsCirChannel.java +++ b/src/com/android/im/imps/SmsCirChannel.java @@ -16,6 +16,8 @@ */ package com.android.im.imps; +import android.util.Log; + import com.android.im.engine.ImErrorInfo; import com.android.im.engine.ImException; import com.android.im.engine.SmsService; @@ -41,13 +43,17 @@ public class SmsCirChannel extends CirChannel @Override public void connect() throws ImException { - if (mAddr == null || mAddr.length() == 0) { - throw new ImException(ImpsErrorInfo.UNKNOWN_SERVER, - "Invalid sms addr"); - } mSmsService = SystemService.getDefault().getSmsService(); - mSmsService.addSmsListener(mAddr, mPort, this); - sendHelo(); + if (mAddr != null) { + mSmsService.addSmsListener(mAddr, mPort, this); + sendHelo(); + } else { + mSmsService.addSmsListener(SmsService.ANY_ADDRESS, mPort, this); + } + } + + public boolean isShutdown() { + return false; } @Override @@ -59,23 +65,23 @@ public class SmsCirChannel extends CirChannel // It's safe to assume that each character is encoded into 7-bit since // all characters in CIR are in gsm 7-bit alphabet. int lengthSeptets = data.length * 8 / 7; - int numPaddingBits = data.length * 8 % 7; String s = GsmAlphabet.gsm7BitPackedToString(data, 0, - lengthSeptets, numPaddingBits); + lengthSeptets, 0); // CIR format: WVCI if (!s.startsWith("WVCI")) { // not a valid CIR, ignore. + Log.w("SmsCir", "Received a non-CIR SMS, ignore!"); return; } + + String sessionCookie = mConnection.getSession().getCookie(); String[] fields = s.split(" "); - if (fields.length != 3) { + if (fields.length != 3 || !sessionCookie.equalsIgnoreCase(fields[2])) { // Not a valid CIR, ignore - return; - } - String sessionCookie = mConnection.getSession().getCookie(); - if (sessionCookie.equalsIgnoreCase(fields[2])) { - mConnection.sendPollingRequest(); + Log.w("SmsCir", "The CIR format is not correct or session cookie" + + " does not match"); } + mConnection.sendPollingRequest(); } public void onFailure(int errorCode) { diff --git a/src/com/android/im/imps/StandardPasswordDigest.java b/src/com/android/im/imps/StandardPasswordDigest.java index dd7bbaf..e88f377 100644 --- a/src/com/android/im/imps/StandardPasswordDigest.java +++ b/src/com/android/im/imps/StandardPasswordDigest.java @@ -18,6 +18,7 @@ package com.android.im.imps; import com.android.im.engine.ImException; +import com.android.im.plugin.PasswordDigest; import org.apache.commons.codec.binary.Base64; diff --git a/src/com/android/im/imps/TcpCirChannel.java b/src/com/android/im/imps/TcpCirChannel.java index 6679a0a..45dfdcd 100644 --- a/src/com/android/im/imps/TcpCirChannel.java +++ b/src/com/android/im/imps/TcpCirChannel.java @@ -62,6 +62,7 @@ class TcpCirChannel extends CirChannel implements Runnable, HeartbeatService.Cal @Override public synchronized void connect() throws ImException { try { + mDone = false; connectServer(); mCirThread = new Thread(this, "TcpCirChannel"); mCirThread.setDaemon(true); @@ -86,8 +87,8 @@ class TcpCirChannel extends CirChannel implements Runnable, HeartbeatService.Cal ImpsLog.log(mUser + " Shutting down CIR channel"); } mDone = true; - synchronized (mReconnectLock) { - if (mReconnecting) { + if (mReconnecting) { + synchronized (mReconnectLock) { mReconnecting = false; mReconnectLock.notify(); } @@ -106,6 +107,10 @@ class TcpCirChannel extends CirChannel implements Runnable, HeartbeatService.Cal } } + public boolean isShutdown() { + return mDone; + } + public void run() { while (!mDone) { try { diff --git a/src/com/android/im/provider/Imps.java b/src/com/android/im/provider/Imps.java new file mode 100644 index 0000000..091e993 --- /dev/null +++ b/src/com/android/im/provider/Imps.java @@ -0,0 +1,2333 @@ +/* + * 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.im.provider; + +import android.content.ContentQueryMap; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.Handler; +import android.provider.BaseColumns; + +import java.util.HashMap; + +/** + * The IM provider stores all information about roster contacts, chat messages, presence, etc. + * + * @hide + */ +public class Imps { + /** + * no public constructor since this is a utility class + */ + private Imps() {} + + /** + * The Columns for IM providers (i.e. AIM, Y!, GTalk) + */ + public interface ProviderColumns { + /** + * The name of the IM provider + *

Type: TEXT

+ */ + String NAME = "name"; + + /** + * The full name of the provider + *

Type: TEXT

+ */ + String FULLNAME = "fullname"; + + /** + * The category for the provider, used to form intent. + *

Type: TEXT

+ */ + String CATEGORY = "category"; + + /** + * The url users should visit to create a new account for this provider + *

Type: TEXT

+ */ + String SIGNUP_URL = "signup_url"; + } + + /** + * Known names corresponding to the {@link ProviderColumns#NAME} column + */ + public interface ProviderNames { + // + //NOTE: update Contacts.java with new providers when they're added. + // + String YAHOO = "Yahoo"; + String GTALK = "GTalk"; + String MSN = "MSN"; + String ICQ = "ICQ"; + String AIM = "AIM"; + String XMPP = "XMPP"; + String JABBER = "JABBER"; + String SKYPE = "SKYPE"; + String QQ = "QQ"; + } + + /** + * This table contains the IM providers + */ + public static final class Provider implements BaseColumns, ProviderColumns { + private Provider() {} + + public static final long getProviderIdForName(ContentResolver cr, String providerName) { + String[] selectionArgs = new String[1]; + selectionArgs[0] = providerName; + + Cursor cursor = cr.query(CONTENT_URI, + PROVIDER_PROJECTION, + NAME+"=?", + selectionArgs, null); + + long retVal = 0; + try { + if (cursor.moveToFirst()) { + retVal = cursor.getLong(cursor.getColumnIndexOrThrow(_ID)); + } + } finally { + cursor.close(); + } + + return retVal; + } + + public static final String getProviderNameForId(ContentResolver cr, long providerId) { + Cursor cursor = cr.query(CONTENT_URI, + PROVIDER_PROJECTION, + _ID + "=" + providerId, + null, null); + + String retVal = null; + try { + if (cursor.moveToFirst()) { + retVal = cursor.getString(cursor.getColumnIndexOrThrow(NAME)); + } + } finally { + cursor.close(); + } + + return retVal; + } + + private static final String[] PROVIDER_PROJECTION = new String[] { + _ID, + NAME + }; + + public static final String ACTIVE_ACCOUNT_ID = "account_id"; + public static final String ACTIVE_ACCOUNT_USERNAME = "account_username"; + public static final String ACTIVE_ACCOUNT_PW = "account_pw"; + public static final String ACTIVE_ACCOUNT_LOCKED = "account_locked"; + public static final String ACTIVE_ACCOUNT_KEEP_SIGNED_IN = "account_keepSignedIn"; + public static final String ACCOUNT_PRESENCE_STATUS = "account_presenceStatus"; + public static final String ACCOUNT_CONNECTION_STATUS = "account_connStatus"; + + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://imps/providers"); + + public static final Uri CONTENT_URI_WITH_ACCOUNT = + Uri.parse("content://imps/providers/account"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of + * people. + */ + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/imps-providers"; + + public static final String CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/imps-providers"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = "name ASC"; + } + + /** + * The columns for IM accounts. There can be more than one account for each IM provider. + */ + public interface AccountColumns { + /** + * The name of the account + *

Type: TEXT

+ */ + String NAME = "name"; + + /** + * The IM provider for this account + *

Type: INTEGER

+ */ + String PROVIDER = "provider"; + + /** + * The username for this account + *

Type: TEXT

+ */ + String USERNAME = "username"; + + /** + * The password for this account + *

Type: TEXT

+ */ + String PASSWORD = "pw"; + + /** + * A boolean value indicates if the account is active. + *

Type: INTEGER

+ */ + String ACTIVE = "active"; + + /** + * A boolean value indicates if the account is locked (not editable) + *

Type: INTEGER

+ */ + String LOCKED = "locked"; + + /** + * A boolean value to indicate whether this account is kept signed in. + *

Type: INTEGER

+ */ + String KEEP_SIGNED_IN = "keep_signed_in"; + + /** + * A boolean value indiciating the last login state for this account + *

Type: INTEGER

+ */ + String LAST_LOGIN_STATE = "last_login_state"; + } + + /** + * This table contains the IM accounts. + */ + public static final class Account implements BaseColumns, AccountColumns { + private Account() {} + + public static final long getProviderIdForAccount(ContentResolver cr, long accountId) { + Cursor cursor = cr.query(CONTENT_URI, + PROVIDER_PROJECTION, + _ID + "=" + accountId, + null /* selection args */, + null /* sort order */); + + long providerId = 0; + + try { + if (cursor.moveToFirst()) { + providerId = cursor.getLong(PROVIDER_COLUMN); + } + } finally { + cursor.close(); + } + + return providerId; + } + + private static final String[] PROVIDER_PROJECTION = new String[] { PROVIDER }; + private static final int PROVIDER_COLUMN = 0; + + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://imps/accounts"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of + * account. + */ + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/imps-accounts"; + + /** + * The MIME type of a {@link #CONTENT_URI} subdirectory of a single + * account. + */ + public static final String CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/imps-accounts"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = "name ASC"; + + } + + /** + * Connection status + */ + public interface ConnectionStatus { + /** + * The connection is offline, not logged in. + */ + int OFFLINE = 0; + + /** + * The connection is attempting to connect. + */ + int CONNECTING = 1; + + /** + * The connection is suspended due to network not available. + */ + int SUSPENDED = 2; + + /** + * The connection is logged in and online. + */ + int ONLINE = 3; + } + + public interface AccountStatusColumns { + /** + * account id + *

Type: INTEGER

+ */ + String ACCOUNT = "account"; + + /** + * User's presence status, see definitions in {#link CommonPresenceColumn} + *

Type: INTEGER

+ */ + String PRESENCE_STATUS = "presenceStatus"; + + /** + * The connection status of this account, see {#link ConnectionStatus} + *

Type: INTEGER

+ */ + String CONNECTION_STATUS = "connStatus"; + } + + public static final class AccountStatus implements BaseColumns, AccountStatusColumns { + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://imps/accountStatus"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of account status. + */ + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/imps-account-status"; + + /** + * The MIME type of a {@link #CONTENT_URI} subdirectory of a single account status. + */ + public static final String CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/imps-account-status"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = "name ASC"; + } + + /** + * Columns from the Contacts table. + */ + public interface ContactsColumns { + /** + * The username + *

Type: TEXT

+ */ + String USERNAME = "username"; + + /** + * The nickname or display name + *

Type: TEXT

+ */ + String NICKNAME = "nickname"; + + /** + * The IM provider for this contact + *

Type: INTEGER

+ */ + String PROVIDER = "provider"; + + /** + * The account (within a IM provider) for this contact + *

Type: INTEGER

+ */ + String ACCOUNT = "account"; + + /** + * The contactList this contact belongs to + *

Type: INTEGER

+ */ + String CONTACTLIST = "contactList"; + + /** + * Contact type + *

Type: INTEGER

+ */ + String TYPE = "type"; + + /** + * normal IM contact + */ + int TYPE_NORMAL = 0; + /** + * temporary contact, someone not in the list of contacts that we + * subscribe presence for. Usually created because of the user is + * having a chat session with this contact. + */ + int TYPE_TEMPORARY = 1; + /** + * temporary contact created for group chat. + */ + int TYPE_GROUP = 2; + /** + * blocked contact. + */ + int TYPE_BLOCKED = 3; + /** + * the contact is hidden. The client should always display this contact to the user. + */ + int TYPE_HIDDEN = 4; + /** + * the contact is pinned. The client should always display this contact to the user. + */ + int TYPE_PINNED = 5; + + /** + * Contact subscription status + *

Type: INTEGER

+ */ + String SUBSCRIPTION_STATUS = "subscriptionStatus"; + + /** + * no pending subscription + */ + int SUBSCRIPTION_STATUS_NONE = 0; + /** + * requested to subscribe + */ + int SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING = 1; + /** + * requested to unsubscribe + */ + int SUBSCRIPTION_STATUS_UNSUBSCRIBE_PENDING = 2; + + /** + * Contact subscription type + *

Type: INTEGER

+ */ + String SUBSCRIPTION_TYPE = "subscriptionType"; + + /** + * The user and contact have no interest in each other's presence. + */ + int SUBSCRIPTION_TYPE_NONE = 0; + /** + * The user wishes to stop receiving presence updates from the contact. + */ + int SUBSCRIPTION_TYPE_REMOVE = 1; + /** + * The user is interested in receiving presence updates from the contact. + */ + int SUBSCRIPTION_TYPE_TO = 2; + /** + * The contact is interested in receiving presence updates from the user. + */ + int SUBSCRIPTION_TYPE_FROM = 3; + /** + * The user and contact have a mutual interest in each other's presence. + */ + int SUBSCRIPTION_TYPE_BOTH = 4; + /** + * This is a special type reserved for pending subscription requests + */ + int SUBSCRIPTION_TYPE_INVITATIONS = 5; + + /** + * Quick Contact: derived from Google Contact Extension's "message_count" attribute. + *

Type: INTEGER

+ */ + String QUICK_CONTACT = "qc"; + + /** + * Google Contact Extension attribute + * + * Rejected: a boolean value indicating whether a subscription request from + * this client was ever rejected by the user. "true" indicates that it has. + * This is provided so that a client can block repeated subscription requests. + *

Type: INTEGER

+ */ + String REJECTED = "rejected"; + + /** + * Off The Record status: 0 for disabled, 1 for enabled + *

Type: INTEGER

+ */ + String OTR = "otr"; + } + + /** + * This defines the different type of values of {@link ContactsColumns#OTR} + */ + public interface OffTheRecordType { + /* + * Off the record not turned on + */ + int DISABLED = 0; + /** + * Off the record turned on, but we don't know who turned it on + */ + int ENABLED = 1; + /** + * Off the record turned on by the user + */ + int ENABLED_BY_USER = 2; + /** + * Off the record turned on by the buddy + */ + int ENABLED_BY_BUDDY = 3; + }; + + /** + * This table contains contacts. + */ + public static final class Contacts implements BaseColumns, + ContactsColumns, PresenceColumns, ChatsColumns { + /** + * no public constructor since this is a utility class + */ + private Contacts() {} + + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://imps/contacts"); + + /** + * The content:// style URL for contacts joined with presence + */ + public static final Uri CONTENT_URI_WITH_PRESENCE = + Uri.parse("content://imps/contactsWithPresence"); + + /** + * The content:// style URL for barebone contacts, not joined with any other table + */ + public static final Uri CONTENT_URI_CONTACTS_BAREBONE = + Uri.parse("content://imps/contactsBarebone"); + + /** + * The content:// style URL for contacts who have an open chat session + */ + public static final Uri CONTENT_URI_CHAT_CONTACTS = + Uri.parse("content://imps/contacts/chatting"); + + /** + * The content:// style URL for contacts who have been blocked + */ + public static final Uri CONTENT_URI_BLOCKED_CONTACTS = + Uri.parse("content://imps/contacts/blocked"); + + /** + * The content:// style URL for contacts by provider and account + */ + public static final Uri CONTENT_URI_CONTACTS_BY = + Uri.parse("content://imps/contacts"); + + /** + * The content:// style URL for contacts by provider and account, + * and who have an open chat session + */ + public static final Uri CONTENT_URI_CHAT_CONTACTS_BY = + Uri.parse("content://imps/contacts/chatting"); + + /** + * The content:// style URL for contacts by provider and account, + * and who are online + */ + public static final Uri CONTENT_URI_ONLINE_CONTACTS_BY = + Uri.parse("content://imps/contacts/online"); + + /** + * The content:// style URL for contacts by provider and account, + * and who are offline + */ + public static final Uri CONTENT_URI_OFFLINE_CONTACTS_BY = + Uri.parse("content://imps/contacts/offline"); + + /** + * The content:// style URL for operations on bulk contacts + */ + public static final Uri BULK_CONTENT_URI = + Uri.parse("content://imps/bulk_contacts"); + + /** + * The content:// style URL for the count of online contacts in each + * contact list by provider and account. + */ + public static final Uri CONTENT_URI_ONLINE_COUNT = + Uri.parse("content://imps/contacts/onlineCount"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of + * people. + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/imps-contacts"; + + /** + * The MIME type of a {@link #CONTENT_URI} subdirectory of a single + * person. + */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/imps-contacts"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = + "subscriptionType DESC, last_message_date DESC," + + " mode DESC, nickname COLLATE UNICODE ASC"; + + public static final String CHATS_CONTACT = "chats_contact"; + + public static final String AVATAR_HASH = "avatars_hash"; + + public static final String AVATAR_DATA = "avatars_data"; + } + + /** + * Columns from the ContactList table. + */ + public interface ContactListColumns { + String NAME = "name"; + String PROVIDER = "provider"; + String ACCOUNT = "account"; + } + + /** + * This table contains the contact lists. + */ + public static final class ContactList implements BaseColumns, + ContactListColumns { + private ContactList() {} + + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://imps/contactLists"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of + * people. + */ + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/imps-contactLists"; + + /** + * The MIME type of a {@link #CONTENT_URI} subdirectory of a single + * person. + */ + public static final String CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/imps-contactLists"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = "name COLLATE UNICODE ASC"; + + public static final String PROVIDER_NAME = "provider_name"; + + public static final String ACCOUNT_NAME = "account_name"; + } + + /** + * Columns from the BlockedList table. + */ + public interface BlockedListColumns { + /** + * The username of the blocked contact. + *

Type: TEXT

+ */ + String USERNAME = "username"; + + /** + * The nickname of the blocked contact. + *

Type: TEXT

+ */ + String NICKNAME = "nickname"; + + /** + * The provider id of the blocked contact. + *

Type: INT

+ */ + String PROVIDER = "provider"; + + /** + * The account id of the blocked contact. + *

Type: INT

+ */ + String ACCOUNT = "account"; + } + + /** + * This table contains blocked lists + */ + public static final class BlockedList implements BaseColumns, BlockedListColumns { + private BlockedList() {} + + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://imps/blockedList"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of + * people. + */ + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/imps-blockedList"; + + /** + * The MIME type of a {@link #CONTENT_URI} subdirectory of a single + * person. + */ + public static final String CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/imps-blockedList"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = "nickname ASC"; + + public static final String PROVIDER_NAME = "provider_name"; + + public static final String ACCOUNT_NAME = "account_name"; + + public static final String AVATAR_DATA = "avatars_data"; + } + + /** + * Columns from the contactsEtag table + */ + public interface ContactsEtagColumns { + /** + * The roster etag, computed by the server, stored on the client. There is one etag + * per account roster. + *

Type: TEXT

+ */ + String ETAG = "etag"; + + /** + * The OTR etag, computed by the server, stored on the client. There is one OTR etag + * per account roster. + *

Type: TEXT

+ */ + String OTR_ETAG = "otr_etag"; + + /** + * The account id for the etag. + *

Type: INTEGER

+ */ + String ACCOUNT = "account"; + } + + public static final class ContactsEtag implements BaseColumns, ContactsEtagColumns { + private ContactsEtag() {} + + public static final Cursor query(ContentResolver cr, + String[] projection) { + return cr.query(CONTENT_URI, projection, null, null, null); + } + + public static final Cursor query(ContentResolver cr, + String[] projection, String where, String orderBy) { + return cr.query(CONTENT_URI, projection, where, + null, orderBy == null ? null : orderBy); + } + + public static final String getRosterEtag(ContentResolver resolver, long accountId) { + String retVal = null; + + Cursor c = resolver.query(CONTENT_URI, + CONTACT_ETAG_PROJECTION, + ACCOUNT + "=" + accountId, + null /* selection args */, + null /* sort order */); + + try { + if (c.moveToFirst()) { + retVal = c.getString(COLUMN_ETAG); + } + } finally { + c.close(); + } + + return retVal; + } + + public static final String getOtrEtag(ContentResolver resolver, long accountId) { + String retVal = null; + + Cursor c = resolver.query(CONTENT_URI, + CONTACT_OTR_ETAG_PROJECTION, + ACCOUNT + "=" + accountId, + null /* selection args */, + null /* sort order */); + + try { + if (c.moveToFirst()) { + retVal = c.getString(COLUMN_OTR_ETAG); + } + } finally { + c.close(); + } + + return retVal; + } + + private static final String[] CONTACT_ETAG_PROJECTION = new String[] { + Imps.ContactsEtag.ETAG // 0 + }; + + private static int COLUMN_ETAG = 0; + + private static final String[] CONTACT_OTR_ETAG_PROJECTION = new String[] { + Imps.ContactsEtag.OTR_ETAG // 0 + }; + + private static int COLUMN_OTR_ETAG = 0; + + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://imps/contactsEtag"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of + * people. + */ + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/imps-contactsEtag"; + + /** + * The MIME type of a {@link #CONTENT_URI} subdirectory of a single + * person. + */ + public static final String CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/imps-contactsEtag"; + } + + /** + * Message type definition + */ + public interface MessageType { + /* sent message */ + int OUTGOING = 0; + /* received message */ + int INCOMING = 1; + /* presence became available */ + int PRESENCE_AVAILABLE = 2; + /* presence became away */ + int PRESENCE_AWAY = 3; + /* presence became DND (busy) */ + int PRESENCE_DND = 4; + /* presence became unavailable */ + int PRESENCE_UNAVAILABLE = 5; + /* the message is converted to a group chat */ + int CONVERT_TO_GROUPCHAT = 6; + /* generic status */ + int STATUS = 7; + /* the message cannot be sent now, but will be sent later */ + int POSTPONED = 8; + /* off The Record status is turned off */ + int OTR_IS_TURNED_OFF = 9; + /* off the record status is turned on */ + int OTR_IS_TURNED_ON = 10; + /* off the record status turned on by user */ + int OTR_TURNED_ON_BY_USER = 11; + /* off the record status turned on by buddy */ + int OTR_TURNED_ON_BY_BUDDY = 12; + } + + /** + * The common columns for messages table + */ + public interface MessageColumns { + /** + * The thread_id column stores the contact id of the contact the message belongs to. + * For groupchat messages, the thread_id stores the group id, which is the contact id + * of the temporary group contact created for the groupchat. So there should be no + * collision between groupchat message thread id and regular message thread id. + */ + String THREAD_ID = "thread_id"; + + /** + * The nickname. This is used for groupchat messages to indicate the participant's + * nickname. For non groupchat messages, this field should be left empty. + */ + String NICKNAME = "nickname"; + + /** + * The body + *

Type: TEXT

+ */ + String BODY = "body"; + + /** + * The date this message is sent or received + *

Type: INTEGER

+ */ + String DATE = "date"; + + /** + * Message Type, see {@link MessageType} + *

Type: INTEGER

+ */ + String TYPE = "type"; + + /** + * Error Code: 0 means no error. + *

Type: INTEGER

+ */ + String ERROR_CODE = "err_code"; + + /** + * Error Message + *

Type: TEXT

+ */ + String ERROR_MESSAGE = "err_msg"; + + /** + * Packet ID, auto assigned by the GTalkService for outgoing messages or the + * GTalk server for incoming messages. The packet id field is optional for messages, + * so it could be null. + *

Type: STRING

+ */ + String PACKET_ID = "packet_id"; + + /** + * Is groupchat message or not + *

Type: INTEGER

+ */ + String IS_GROUP_CHAT = "is_muc"; + + /** + * A hint that the UI should show the sent time of this message + *

Type: INTEGER

+ */ + String DISPLAY_SENT_TIME = "show_ts"; + } + + /** + * This table contains messages. + */ + public static final class Messages implements BaseColumns, MessageColumns { + /** + * no public constructor since this is a utility class + */ + private Messages() {} + + /** + * Gets the Uri to query messages by thread id. + * + * @param threadId the thread id of the message. + * @return the Uri + */ + public static final Uri getContentUriByThreadId(long threadId) { + Uri.Builder builder = CONTENT_URI_MESSAGES_BY_THREAD_ID.buildUpon(); + ContentUris.appendId(builder, threadId); + return builder.build(); + } + + /** + * @deprecated + * + * Gets the Uri to query messages by account and contact. + * + * @param accountId the account id of the contact. + * @param username the user name of the contact. + * @return the Uri + */ + public static final Uri getContentUriByContact(long accountId, String username) { + Uri.Builder builder = CONTENT_URI_MESSAGES_BY_ACCOUNT_AND_CONTACT.buildUpon(); + ContentUris.appendId(builder, accountId); + builder.appendPath(username); + return builder.build(); + } + + /** + * Gets the Uri to query messages by provider. + * + * @param providerId the service provider id. + * @return the Uri + */ + public static final Uri getContentUriByProvider(long providerId) { + Uri.Builder builder = CONTENT_URI_MESSAGES_BY_PROVIDER.buildUpon(); + ContentUris.appendId(builder, providerId); + return builder.build(); + } + + /** + * Gets the Uri to query off the record messages by account. + * + * @param accountId the account id. + * @return the Uri + */ + public static final Uri getContentUriByAccount(long accountId) { + Uri.Builder builder = CONTENT_URI_BY_ACCOUNT.buildUpon(); + ContentUris.appendId(builder, accountId); + return builder.build(); + } + + /** + * Gets the Uri to query off the record messages by thread id. + * + * @param threadId the thread id of the message. + * @return the Uri + */ + public static final Uri getOtrMessagesContentUriByThreadId(long threadId) { + Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_THREAD_ID.buildUpon(); + ContentUris.appendId(builder, threadId); + return builder.build(); + } + + /** + * @deprecated + * + * Gets the Uri to query off the record messages by account and contact. + * + * @param accountId the account id of the contact. + * @param username the user name of the contact. + * @return the Uri + */ + public static final Uri getOtrMessagesContentUriByContact(long accountId, String username) { + Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT_AND_CONTACT.buildUpon(); + ContentUris.appendId(builder, accountId); + builder.appendPath(username); + return builder.build(); + } + + /** + * Gets the Uri to query off the record messages by provider. + * + * @param providerId the service provider id. + * @return the Uri + */ + public static final Uri getOtrMessagesContentUriByProvider(long providerId) { + Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_PROVIDER.buildUpon(); + ContentUris.appendId(builder, providerId); + return builder.build(); + } + + /** + * Gets the Uri to query off the record messages by account. + * + * @param accountId the account id. + * @return the Uri + */ + public static final Uri getOtrMessagesContentUriByAccount(long accountId) { + Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT.buildUpon(); + ContentUris.appendId(builder, accountId); + return builder.build(); + } + + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://imps/messages"); + + /** + * The content:// style URL for messages by thread id + */ + public static final Uri CONTENT_URI_MESSAGES_BY_THREAD_ID = + Uri.parse("content://imps/messagesByThreadId"); + + /** + * The content:// style URL for messages by account and contact + */ + public static final Uri CONTENT_URI_MESSAGES_BY_ACCOUNT_AND_CONTACT = + Uri.parse("content://imps/messagesByAcctAndContact"); + + /** + * The content:// style URL for messages by provider + */ + public static final Uri CONTENT_URI_MESSAGES_BY_PROVIDER = + Uri.parse("content://imps/messagesByProvider"); + + /** + * The content:// style URL for messages by account + */ + public static final Uri CONTENT_URI_BY_ACCOUNT = + Uri.parse("content://imps/messagesByAccount"); + + /** + * The content:// style url for off the record messages + */ + public static final Uri OTR_MESSAGES_CONTENT_URI = + Uri.parse("content://imps/otrMessages"); + + /** + * The content:// style url for off the record messages by thread id + */ + public static final Uri OTR_MESSAGES_CONTENT_URI_BY_THREAD_ID = + Uri.parse("content://imps/otrMessagesByThreadId"); + + /** + * The content:// style url for off the record messages by account and contact + */ + public static final Uri OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT_AND_CONTACT = + Uri.parse("content://imps/otrMessagesByAcctAndContact"); + + /** + * The content:// style URL for off the record messages by provider + */ + public static final Uri OTR_MESSAGES_CONTENT_URI_BY_PROVIDER = + Uri.parse("content://imps/otrMessagesByProvider"); + + /** + * The content:// style URL for off the record messages by account + */ + public static final Uri OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT = + Uri.parse("content://imps/otrMessagesByAccount"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of + * people. + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/imps-messages"; + + /** + * The MIME type of a {@link #CONTENT_URI} subdirectory of a single + * person. + */ + public static final String CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/imps-messages"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = "date ASC"; + + /** + * The "contact" column. This is not a real column in the messages table, but a + * temoprary column created when querying for messages (joined with the contacts table) + */ + public static final String CONTACT = "contact"; + } + + /** + * Columns for the GroupMember table. + */ + public interface GroupMemberColumns { + /** + * The id of the group this member belongs to. + *

Type: INTEGER

+ */ + String GROUP = "groupId"; + + /** + * The full name of this member. + *

Type: TEXT

+ */ + String USERNAME = "username"; + + /** + * The nick name of this member. + *

Type: TEXT

+ */ + String NICKNAME = "nickname"; + } + + public final static class GroupMembers implements GroupMemberColumns { + private GroupMembers(){} + + public static final Uri CONTENT_URI = + Uri.parse("content://imps/groupMembers"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of + * group members. + */ + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/imps-groupMembers"; + + /** + * The MIME type of a {@link #CONTENT_URI} subdirectory of a single + * group member. + */ + public static final String CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/imps-groupMembers"; + } + + /** + * Columns from the Invitation table. + */ + public interface InvitationColumns { + /** + * The provider id. + *

Type: INTEGER

+ */ + String PROVIDER = "providerId"; + + /** + * The account id. + *

Type: INTEGER

+ */ + String ACCOUNT = "accountId"; + + /** + * The invitation id. + *

Type: TEXT

+ */ + String INVITE_ID = "inviteId"; + + /** + * The name of the sender of the invitation. + *

Type: TEXT

+ */ + String SENDER = "sender"; + + /** + * The name of the group which the sender invite you to join. + *

Type: TEXT

+ */ + String GROUP_NAME = "groupName"; + + /** + * A note + *

Type: TEXT

+ */ + String NOTE = "note"; + + /** + * The current status of the invitation. + *

Type: TEXT

+ */ + String STATUS = "status"; + + int STATUS_PENDING = 0; + int STATUS_ACCEPTED = 1; + int STATUS_REJECTED = 2; + } + + /** + * This table contains the invitations received from others. + */ + public final static class Invitation implements InvitationColumns, + BaseColumns { + private Invitation() { + } + + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://imps/invitations"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of + * invitations. + */ + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/imps-invitations"; + + /** + * The MIME type of a {@link #CONTENT_URI} subdirectory of a single + * invitation. + */ + public static final String CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/imps-invitations"; + } + + /** + * Columns from the Avatars table + */ + public interface AvatarsColumns { + /** + * The contact this avatar belongs to + *

Type: TEXT

+ */ + String CONTACT = "contact"; + + String PROVIDER = "provider_id"; + + String ACCOUNT = "account_id"; + + /** + * The hash of the image data + *

Type: TEXT

+ */ + String HASH = "hash"; + + /** + * raw image data + *

Type: BLOB

+ */ + String DATA = "data"; + } + + /** + * This table contains avatars. + */ + public static final class Avatars implements BaseColumns, AvatarsColumns { + /** + * no public constructor since this is a utility class + */ + private Avatars() {} + + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = Uri.parse("content://imps/avatars"); + + /** + * The content:// style URL for avatars by provider, account and contact + */ + public static final Uri CONTENT_URI_AVATARS_BY = + Uri.parse("content://imps/avatarsBy"); + + /** + * The MIME type of {@link #CONTENT_URI} providing the avatars + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/imps-avatars"; + + /** + * The MIME type of a {@link #CONTENT_URI} + */ + public static final String CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/imps-avatars"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = "contact ASC"; + + } + + /** + * Common presence columns shared between the IM and contacts presence tables + */ + public interface CommonPresenceColumns { + /** + * The priority, an integer, used by XMPP presence + *

Type: INTEGER

+ */ + String PRIORITY = "priority"; + + /** + * The server defined status. + *

Type: INTEGER (one of the values below)

+ */ + String PRESENCE_STATUS = "mode"; + + /** + * Presence Status definition + */ + int OFFLINE = 0; + int INVISIBLE = 1; + int AWAY = 2; + int IDLE = 3; + int DO_NOT_DISTURB = 4; + int AVAILABLE = 5; + + /** + * The user defined status line. + *

Type: TEXT

+ */ + String PRESENCE_CUSTOM_STATUS = "status"; + } + + /** + * Columns from the Presence table. + */ + public interface PresenceColumns extends CommonPresenceColumns { + /** + * The contact id + *

Type: INTEGER

+ */ + String CONTACT_ID = "contact_id"; + + /** + * The contact's JID resource, only relevant for XMPP contact + *

Type: TEXT

+ */ + String JID_RESOURCE = "jid_resource"; + + /** + * The contact's client type + */ + String CLIENT_TYPE = "client_type"; + + /** + * client type definitions + */ + int CLIENT_TYPE_DEFAULT = 0; + int CLIENT_TYPE_MOBILE = 1; + int CLIENT_TYPE_ANDROID = 2; + } + + /** + * Contains presence infomation for contacts. + */ + public static final class Presence implements BaseColumns, PresenceColumns { + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = Uri.parse("content://imps/presence"); + + /** + * The content URL for IM presences for an account + */ + public static final Uri CONTENT_URI_BY_ACCOUNT = Uri.parse("content://imps/presence/account"); + + /** + * The content:// style URL for operations on bulk contacts + */ + public static final Uri BULK_CONTENT_URI = Uri.parse("content://imps/bulk_presence"); + + /** + * The content:// style URL for seeding presences for a given account id. + */ + public static final Uri SEED_PRESENCE_BY_ACCOUNT_CONTENT_URI = + Uri.parse("content://imps/seed_presence/account"); + + /** + * The MIME type of a {@link #CONTENT_URI} providing a directory of presence + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/imps-presence"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = "mode DESC"; + } + + /** + * Columns from the Chats table. + */ + public interface ChatsColumns { + /** + * The contact ID this chat belongs to. The value is a long. + *

Type: INT

+ */ + String CONTACT_ID = "contact_id"; + + /** + * The GTalk JID resource. The value is a string. + *

Type: TEXT

+ */ + String JID_RESOURCE = "jid_resource"; + + /** + * Whether this is a groupchat or not. + *

Type: INT

+ */ + String GROUP_CHAT = "groupchat"; + + /** + * The last unread message. This both indicates that there is an + * unread message, and what the message is. + *

Type: TEXT

+ */ + String LAST_UNREAD_MESSAGE = "last_unread_message"; + + /** + * The last message timestamp + *

Type: INT

+ */ + String LAST_MESSAGE_DATE = "last_message_date"; + + /** + * A message that is being composed. This indicates that there was a + * message being composed when the chat screen was shutdown, and what the + * message is. + *

Type: TEXT

+ */ + String UNSENT_COMPOSED_MESSAGE = "unsent_composed_message"; + + /** + * A value from 0-9 indicating which quick-switch chat screen slot this + * chat is occupying. If none (for instance, this is the 12th active chat) + * then the value is -1. + *

Type: INT

+ */ + String SHORTCUT = "shortcut"; + } + + /** + * Contains ongoing chat sessions. + */ + public static final class Chats implements BaseColumns, ChatsColumns { + /** + * no public constructor since this is a utility class + */ + private Chats() {} + + /** + * The content:// style URL for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://imps/chats"); + + /** + * The content URL for all chats that belong to the account + */ + public static final Uri CONTENT_URI_BY_ACCOUNT = Uri.parse("content://imps/chats/account"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of chats. + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/imps-chats"; + + /** + * The MIME type of a {@link #CONTENT_URI} subdirectory of a single chat. + */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/imps-chats"; + + /** + * The default sort order for this table + */ + public static final String DEFAULT_SORT_ORDER = "last_message_date ASC"; + } + + /** + * Columns from session cookies table. Used for IMPS. + */ + public static interface SessionCookiesColumns { + String NAME = "name"; + String VALUE = "value"; + String PROVIDER = "provider"; + String ACCOUNT = "account"; + } + + /** + * Contains IMPS session cookies. + */ + public static class SessionCookies implements SessionCookiesColumns, BaseColumns { + private SessionCookies() { + } + + /** + * The content:// style URI for this table + */ + public static final Uri CONTENT_URI = Uri.parse("content://imps/sessionCookies"); + + /** + * The content:// style URL for session cookies by provider and account + */ + public static final Uri CONTENT_URI_SESSION_COOKIES_BY = + Uri.parse("content://imps/sessionCookiesBy"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of + * people. + */ + public static final String CONTENT_TYPE = "vnd.android-dir/imps-sessionCookies"; + } + + /** + * Columns from ProviderSettings table + */ + public static interface ProviderSettingsColumns { + /** + * The id in database of the related provider + * + *

Type: INT

+ */ + String PROVIDER = "provider"; + + /** + * The name of the setting + *

Type: TEXT

+ */ + String NAME = "name"; + + /** + * The value of the setting + *

Type: TEXT

+ */ + String VALUE = "value"; + } + + public static class ProviderSettings implements ProviderSettingsColumns { + private ProviderSettings() { + } + + /** + * The content:// style URI for this table + */ + public static final Uri CONTENT_URI = + Uri.parse("content://imps/providerSettings"); + + /** + * The MIME type of {@link #CONTENT_URI} providing provider settings + */ + public static final String CONTENT_TYPE = "vnd.android-dir/imps-providerSettings"; + + /** + * A boolean value to indicate whether this provider should show the offline contacts + */ + public static final String SHOW_OFFLINE_CONTACTS = "show_offline_contacts"; + + /** controls whether or not the GTalk service automatically connect to server. */ + public static final String SETTING_AUTOMATICALLY_CONNECT_GTALK = "gtalk_auto_connect"; + + /** controls whether or not the IM service will be automatically started after boot */ + public static final String SETTING_AUTOMATICALLY_START_SERVICE = "auto_start_service"; + + /** controls whether or not the offline contacts will be hided */ + public static final String SETTING_HIDE_OFFLINE_CONTACTS = "hide_offline_contacts"; + + /** controls whether or not enable the IM notification */ + public static final String SETTING_ENABLE_NOTIFICATION = "enable_notification"; + + /** specifies whether or not to vibrate */ + public static final String SETTING_VIBRATE = "vibrate"; + + /** specifies the Uri string of the ringtone */ + public static final String SETTING_RINGTONE = "ringtone"; + + /** specifies the Uri of the default ringtone */ + public static final String SETTING_RINGTONE_DEFAULT = + "content://settings/system/notification_sound"; + + /** specifies whether or not to show mobile indicator to friends */ + public static final String SETTING_SHOW_MOBILE_INDICATOR = "mobile_indicator"; + + /** specifies whether or not to show as away when device is idle */ + public static final String SETTING_SHOW_AWAY_ON_IDLE = "show_away_on_idle"; + + /** specifies whether or not to upload heartbeat stat upon login */ + public static final String SETTING_UPLOAD_HEARTBEAT_STAT = "upload_heartbeat_stat"; + + /** specifies the last heartbeat interval received from the server */ + public static final String SETTING_HEARTBEAT_INTERVAL = "heartbeat_interval"; + + /** specifiy the JID resource used for Google Talk connection */ + public static final String SETTING_JID_RESOURCE = "jid_resource"; + + /** + * Used for reliable message queue (RMQ). This is for storing the last rmq id received + * from the GTalk server + */ + public static final String LAST_RMQ_RECEIVED = "last_rmq_rec"; + + /** + * Query the settings of the provider specified by id + * + * @param cr + * the relative content resolver + * @param providerId + * the specified id of provider + * @return a HashMap which contains all the settings for the specified + * provider + */ + public static HashMap queryProviderSettings(ContentResolver cr, + long providerId) { + HashMap settings = new HashMap(); + + String[] projection = { NAME, VALUE }; + Cursor c = cr.query(ContentUris.withAppendedId(CONTENT_URI, providerId), projection, null, null, null); + if (c == null) { + return null; + } + + while(c.moveToNext()) { + settings.put(c.getString(0), c.getString(1)); + } + + c.close(); + + return settings; + } + + /** + * Get the string value of setting which is specified by provider id and the setting name. + * + * @param cr The ContentResolver to use to access the settings table. + * @param providerId The id of the provider. + * @param settingName The name of the setting. + * @return The value of the setting if the setting exist, otherwise return null. + */ + public static String getStringValue(ContentResolver cr, long providerId, String settingName) { + String ret = null; + Cursor c = getSettingValue(cr, providerId, settingName); + if (c != null) { + ret = c.getString(0); + c.close(); + } + + return ret; + } + + /** + * Get the boolean value of setting which is specified by provider id and the setting name. + * + * @param cr The ContentResolver to use to access the settings table. + * @param providerId The id of the provider. + * @param settingName The name of the setting. + * @return The value of the setting if the setting exist, otherwise return false. + */ + public static boolean getBooleanValue(ContentResolver cr, long providerId, String settingName) { + boolean ret = false; + Cursor c = getSettingValue(cr, providerId, settingName); + if (c != null) { + ret = c.getInt(0) != 0; + c.close(); + } + return ret; + } + + private static Cursor getSettingValue(ContentResolver cr, long providerId, String settingName) { + Cursor c = cr.query(ContentUris.withAppendedId(CONTENT_URI, providerId), new String[]{VALUE}, NAME + "=?", + new String[]{settingName}, null); + if (c != null) { + if (!c.moveToFirst()) { + c.close(); + return null; + } + } + return c; + } + + /** + * Save a long value of setting in the table providerSetting. + * + * @param cr The ContentProvider used to access the providerSetting table. + * @param providerId The id of the provider. + * @param name The name of the setting. + * @param value The value of the setting. + */ + public static void putLongValue(ContentResolver cr, long providerId, String name, + long value) { + ContentValues v = new ContentValues(3); + v.put(PROVIDER, providerId); + v.put(NAME, name); + v.put(VALUE, value); + + cr.insert(CONTENT_URI, v); + } + + /** + * Save a boolean value of setting in the table providerSetting. + * + * @param cr The ContentProvider used to access the providerSetting table. + * @param providerId The id of the provider. + * @param name The name of the setting. + * @param value The value of the setting. + */ + public static void putBooleanValue(ContentResolver cr, long providerId, String name, + boolean value) { + ContentValues v = new ContentValues(3); + v.put(PROVIDER, providerId); + v.put(NAME, name); + v.put(VALUE, Boolean.toString(value)); + + cr.insert(CONTENT_URI, v); + } + + /** + * Save a string value of setting in the table providerSetting. + * + * @param cr The ContentProvider used to access the providerSetting table. + * @param providerId The id of the provider. + * @param name The name of the setting. + * @param value The value of the setting. + */ + public static void putStringValue(ContentResolver cr, long providerId, String name, + String value) { + ContentValues v = new ContentValues(3); + v.put(PROVIDER, providerId); + v.put(NAME, name); + v.put(VALUE, value); + + cr.insert(CONTENT_URI, v); + } + + /** + * A convenience method to set whether or not the GTalk service should be started + * automatically. + * + * @param contentResolver The ContentResolver to use to access the settings table + * @param autoConnect Whether the GTalk service should be started automatically. + */ + public static void setAutomaticallyConnectGTalk(ContentResolver contentResolver, + long providerId, boolean autoConnect) { + putBooleanValue(contentResolver, providerId, SETTING_AUTOMATICALLY_CONNECT_GTALK, + autoConnect); + } + + /** + * A convenience method to set whether or not the offline contacts should be hided + * + * @param contentResolver The ContentResolver to use to access the setting table + * @param hideOfflineContacts Whether the offline contacts should be hided + */ + public static void setHideOfflineContacts(ContentResolver contentResolver, + long providerId, boolean hideOfflineContacts) { + putBooleanValue(contentResolver, providerId, SETTING_HIDE_OFFLINE_CONTACTS, + hideOfflineContacts); + } + + /** + * A convenience method to set whether or not enable the IM notification. + * + * @param contentResolver The ContentResolver to use to access the setting table. + * @param enable Whether enable the IM notification + */ + public static void setEnableNotification(ContentResolver contentResolver, long providerId, + boolean enable) { + putBooleanValue(contentResolver, providerId, SETTING_ENABLE_NOTIFICATION, enable); + } + + /** + * A convenience method to set whether or not to vibrate. + * + * @param contentResolver The ContentResolver to use to access the setting table. + * @param vibrate Whether or not to vibrate + */ + public static void setVibrate(ContentResolver contentResolver, long providerId, + boolean vibrate) { + putBooleanValue(contentResolver, providerId, SETTING_VIBRATE, vibrate); + } + + /** + * A convenience method to set the Uri String of the ringtone. + * + * @param contentResolver The ContentResolver to use to access the setting table. + * @param ringtoneUri The Uri String of the ringtone to be set. + */ + public static void setRingtoneURI(ContentResolver contentResolver, long providerId, + String ringtoneUri) { + putStringValue(contentResolver, providerId, SETTING_RINGTONE, ringtoneUri); + } + + /** + * A convenience method to set whether or not to show mobile indicator. + * + * @param contentResolver The ContentResolver to use to access the setting table. + * @param showMobileIndicator Whether or not to show mobile indicator. + */ + public static void setShowMobileIndicator(ContentResolver contentResolver, long providerId, + boolean showMobileIndicator) { + putBooleanValue(contentResolver, providerId, SETTING_SHOW_MOBILE_INDICATOR, + showMobileIndicator); + } + + /** + * A convenience method to set whether or not to show as away when device is idle. + * + * @param contentResolver The ContentResolver to use to access the setting table. + * @param showAway Whether or not to show as away when device is idle. + */ + public static void setShowAwayOnIdle(ContentResolver contentResolver, + long providerId, boolean showAway) { + putBooleanValue(contentResolver, providerId, SETTING_SHOW_AWAY_ON_IDLE, showAway); + } + + /** + * A convenience method to set whether or not to upload heartbeat stat. + * + * @param contentResolver The ContentResolver to use to access the setting table. + * @param uploadStat Whether or not to upload heartbeat stat. + */ + public static void setUploadHeartbeatStat(ContentResolver contentResolver, + long providerId, boolean uploadStat) { + putBooleanValue(contentResolver, providerId, SETTING_UPLOAD_HEARTBEAT_STAT, uploadStat); + } + + /** + * A convenience method to set the heartbeat interval last received from the server. + * + * @param contentResolver The ContentResolver to use to access the setting table. + * @param interval The heartbeat interval last received from the server. + */ + public static void setHeartbeatInterval(ContentResolver contentResolver, + long providerId, long interval) { + putLongValue(contentResolver, providerId, SETTING_HEARTBEAT_INTERVAL, interval); + } + + /** + * A convenience method to set the jid resource. + */ + public static void setJidResource(ContentResolver contentResolver, + long providerId, String jidResource) { + putStringValue(contentResolver, providerId, SETTING_JID_RESOURCE, jidResource); + } + + public static class QueryMap extends ContentQueryMap { + private ContentResolver mContentResolver; + private long mProviderId; + + public QueryMap(ContentResolver contentResolver, long providerId, boolean keepUpdated, + Handler handlerForUpdateNotifications) { + super(contentResolver.query(CONTENT_URI, + new String[] {NAME,VALUE}, + PROVIDER + "=" + providerId, + null, // no selection args + null), // no sort order + NAME, keepUpdated, handlerForUpdateNotifications); + mContentResolver = contentResolver; + mProviderId = providerId; + } + + /** + * Set if the GTalk service should automatically connect to server. + * + * @param autoConnect if the GTalk service should auto connect to server. + */ + public void setAutomaticallyConnectToGTalkServer(boolean autoConnect) { + ProviderSettings.setAutomaticallyConnectGTalk(mContentResolver, mProviderId, + autoConnect); + } + + /** + * Check if the GTalk service should automatically connect to server. + * @return if the GTalk service should automatically connect to server. + */ + public boolean getAutomaticallyConnectToGTalkServer() { + return getBoolean(SETTING_AUTOMATICALLY_CONNECT_GTALK, + true /* default to automatically sign in */); + } + + /** + * Set whether or not the offline contacts should be hided. + * + * @param hideOfflineContacts Whether or not the offline contacts should be hided. + */ + public void setHideOfflineContacts(boolean hideOfflineContacts) { + ProviderSettings.setHideOfflineContacts(mContentResolver, mProviderId, + hideOfflineContacts); + } + + /** + * Check if the offline contacts should be hided. + * + * @return Whether or not the offline contacts should be hided. + */ + public boolean getHideOfflineContacts() { + return getBoolean(SETTING_HIDE_OFFLINE_CONTACTS, + false/* by default not hide the offline contacts*/); + } + + /** + * Set whether or not enable the IM notification. + * + * @param enable Whether or not enable the IM notification. + */ + public void setEnableNotification(boolean enable) { + ProviderSettings.setEnableNotification(mContentResolver, mProviderId, enable); + } + + /** + * Check if the IM notification is enabled. + * + * @return Whether or not enable the IM notification. + */ + public boolean getEnableNotification() { + return getBoolean(SETTING_ENABLE_NOTIFICATION, + true/* by default enable the notification */); + } + + /** + * Set whether or not to vibrate on IM notification. + * + * @param vibrate Whether or not to vibrate. + */ + public void setVibrate(boolean vibrate) { + ProviderSettings.setVibrate(mContentResolver, mProviderId, vibrate); + } + + /** + * Gets whether or not to vibrate on IM notification. + * + * @return Whether or not to vibrate. + */ + public boolean getVibrate() { + return getBoolean(SETTING_VIBRATE, false /* by default disable vibrate */); + } + + /** + * Set the Uri for the ringtone. + * + * @param ringtoneUri The Uri of the ringtone to be set. + */ + public void setRingtoneURI(String ringtoneUri) { + ProviderSettings.setRingtoneURI(mContentResolver, mProviderId, ringtoneUri); + } + + /** + * Get the Uri String of the current ringtone. + * + * @return The Uri String of the current ringtone. + */ + public String getRingtoneURI() { + return getString(SETTING_RINGTONE, SETTING_RINGTONE_DEFAULT); + } + + /** + * Set whether or not to show mobile indicator to friends. + * + * @param showMobile whether or not to show mobile indicator. + */ + public void setShowMobileIndicator(boolean showMobile) { + ProviderSettings.setShowMobileIndicator(mContentResolver, mProviderId, showMobile); + } + + /** + * Gets whether or not to show mobile indicator. + * + * @return Whether or not to show mobile indicator. + */ + public boolean getShowMobileIndicator() { + return getBoolean(SETTING_SHOW_MOBILE_INDICATOR, + true /* by default show mobile indicator */); + } + + /** + * Set whether or not to show as away when device is idle. + * + * @param showAway whether or not to show as away when device is idle. + */ + public void setShowAwayOnIdle(boolean showAway) { + ProviderSettings.setShowAwayOnIdle(mContentResolver, mProviderId, showAway); + } + + /** + * Get whether or not to show as away when device is idle. + * + * @return Whether or not to show as away when device is idle. + */ + public boolean getShowAwayOnIdle() { + return getBoolean(SETTING_SHOW_AWAY_ON_IDLE, + true /* by default show as away on idle*/); + } + + /** + * Set whether or not to upload heartbeat stat. + * + * @param uploadStat whether or not to upload heartbeat stat. + */ + public void setUploadHeartbeatStat(boolean uploadStat) { + ProviderSettings.setUploadHeartbeatStat(mContentResolver, mProviderId, uploadStat); + } + + /** + * Get whether or not to upload heartbeat stat. + * + * @return Whether or not to upload heartbeat stat. + */ + public boolean getUploadHeartbeatStat() { + return getBoolean(SETTING_UPLOAD_HEARTBEAT_STAT, + false /* by default do not upload */); + } + + /** + * Set the last received heartbeat interval from the server. + * + * @param interval the last received heartbeat interval from the server. + */ + public void setHeartbeatInterval(long interval) { + ProviderSettings.setHeartbeatInterval(mContentResolver, mProviderId, interval); + } + + /** + * Get the last received heartbeat interval from the server. + * + * @return the last received heartbeat interval from the server. + */ + public long getHeartbeatInterval() { + return getLong(SETTING_HEARTBEAT_INTERVAL, 0L /* an invalid default interval */); + } + + /** + * Set the JID resource. + * + * @param jidResource the jid resource to be stored. + */ + public void setJidResource(String jidResource) { + ProviderSettings.setJidResource(mContentResolver, mProviderId, jidResource); + } + /** + * Get the JID resource used for the Google Talk connection + * + * @return the JID resource stored. + */ + public String getJidResource() { + return getString(SETTING_JID_RESOURCE, null); + } + + /** + * Convenience function for retrieving a single settings value + * as a boolean. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * @return The setting's current value, or 'def' if it is not defined. + */ + private boolean getBoolean(String name, boolean def) { + ContentValues values = getValues(name); + return values != null ? values.getAsBoolean(VALUE) : def; + } + + /** + * Convenience function for retrieving a single settings value + * as a String. + * + * @param name The name of the setting to retrieve. + * @param def The value to return if the setting is not defined. + * @return The setting's current value or 'def' if it is not defined. + */ + private String getString(String name, String def) { + ContentValues values = getValues(name); + return values != null ? values.getAsString(VALUE) : def; + } + + /** + * Convenience function for retrieving a single settings value + * as an Integer. + * + * @param name The name of the setting to retrieve. + * @param def The value to return if the setting is not defined. + * @return The setting's current value or 'def' if it is not defined. + */ + private int getInteger(String name, int def) { + ContentValues values = getValues(name); + return values != null ? values.getAsInteger(VALUE) : def; + } + + /** + * Convenience function for retrieving a single settings value + * as a Long. + * + * @param name The name of the setting to retrieve. + * @param def The value to return if the setting is not defined. + * @return The setting's current value or 'def' if it is not defined. + */ + private long getLong(String name, long def) { + ContentValues values = getValues(name); + return values != null ? values.getAsLong(VALUE) : def; + } + } + + } + + + /** + * Columns for IM branding resource map cache table. This table caches the result of + * loading the branding resources to speed up IM landing page start. + */ + public interface BrandingResourceMapCacheColumns { + /** + * The provider ID + *

Type: INTEGER

+ */ + String PROVIDER_ID = "provider_id"; + /** + * The application resource ID + *

Type: INTEGER

+ */ + String APP_RES_ID = "app_res_id"; + /** + * The plugin resource ID + *

Type: INTEGER

+ */ + String PLUGIN_RES_ID = "plugin_res_id"; + } + + /** + * The table for caching the result of loading IM branding resources. + */ + public static final class BrandingResourceMapCache + implements BaseColumns, BrandingResourceMapCacheColumns { + /** + * The content:// style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://imps/brandingResMapCache"); + } + + + + /** + * //TODO: move these to MCS specific provider. + * The following are MCS stuff, and should really live in a separate provider specific to + * MCS code. + */ + + /** + * Columns from OutgoingRmq table + */ + public interface OutgoingRmqColumns { + String RMQ_ID = "rmq_id"; + String TIMESTAMP = "ts"; + String DATA = "data"; + String PROTOBUF_TAG = "type"; + } + + /** + * //TODO: we should really move these to their own provider and database. + * The table for storing outgoing rmq packets. + */ + public static final class OutgoingRmq implements BaseColumns, OutgoingRmqColumns { + private static String[] RMQ_ID_PROJECTION = new String[] { + RMQ_ID, + }; + + /** + * queryHighestRmqId + * + * @param resolver the content resolver + * @return the highest rmq id assigned to the rmq packet, or 0 if there are no rmq packets + * in the OutgoingRmq table. + */ + public static final long queryHighestRmqId(ContentResolver resolver) { + Cursor cursor = resolver.query(Imps.OutgoingRmq.CONTENT_URI_FOR_HIGHEST_RMQ_ID, + RMQ_ID_PROJECTION, + null, // selection + null, // selection args + null // sort + ); + + long retVal = 0; + try { + //if (DBG) log("initializeRmqid: cursor.count= " + cursor.count()); + + if (cursor.moveToFirst()) { + retVal = cursor.getLong(cursor.getColumnIndexOrThrow(RMQ_ID)); + } + } finally { + cursor.close(); + } + + return retVal; + } + + /** + * The content:// style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://imps/outgoingRmqMessages"); + + /** + * The content:// style URL for the highest rmq id for the outgoing rmq messages + */ + public static final Uri CONTENT_URI_FOR_HIGHEST_RMQ_ID = + Uri.parse("content://imps/outgoingHighestRmqId"); + + /** + * The default sort order for this table. + */ + public static final String DEFAULT_SORT_ORDER = "rmq_id ASC"; + } + + /** + * Columns for the LastRmqId table, which stores a single row for the last client rmq id + * sent to the server. + */ + public interface LastRmqIdColumns { + String RMQ_ID = "rmq_id"; + } + + /** + * //TODO: move these out into their own provider and database + * The table for storing the last client rmq id sent to the server. + */ + public static final class LastRmqId implements BaseColumns, LastRmqIdColumns { + private static String[] PROJECTION = new String[] { + RMQ_ID, + }; + + /** + * queryLastRmqId + * + * queries the last rmq id saved in the LastRmqId table. + * + * @param resolver the content resolver. + * @return the last rmq id stored in the LastRmqId table, or 0 if not found. + */ + public static final long queryLastRmqId(ContentResolver resolver) { + Cursor cursor = resolver.query(Imps.LastRmqId.CONTENT_URI, + PROJECTION, + null, // selection + null, // selection args + null // sort + ); + + long retVal = 0; + try { + if (cursor.moveToFirst()) { + retVal = cursor.getLong(cursor.getColumnIndexOrThrow(RMQ_ID)); + } + } finally { + cursor.close(); + } + + return retVal; + } + + /** + * saveLastRmqId + * + * saves the rmqId to the lastRmqId table. This will override the existing row if any, + * as we only keep one row of data in this table. + * + * @param resolver the content resolver. + * @param rmqId the rmq id to be saved. + */ + public static final void saveLastRmqId(ContentResolver resolver, long rmqId) { + ContentValues values = new ContentValues(); + + // always replace the first row. + values.put(_ID, 1); + values.put(RMQ_ID, rmqId); + resolver.insert(CONTENT_URI, values); + } + + /** + * The content:// style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://imps/lastRmqId"); + } + + /** + * Columns for the s2dRmqIds table, which stores the server-to-device message + * persistent ids. These are used in the RMQ2 protocol, where in the login request, the + * client selective acks these s2d ids to the server. + */ + public interface ServerToDeviceRmqIdsColumn { + String RMQ_ID = "rmq_id"; + } + + public static final class ServerToDeviceRmqIds implements BaseColumns, + ServerToDeviceRmqIdsColumn { + + /** + * The content:// style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://imps/s2dids"); + } + +} diff --git a/src/com/android/im/provider/ImpsProvider.java b/src/com/android/im/provider/ImpsProvider.java new file mode 100644 index 0000000..0a32d50 --- /dev/null +++ b/src/com/android/im/provider/ImpsProvider.java @@ -0,0 +1,3332 @@ +/* + * 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.im.provider; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.content.ContentResolver; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteConstraintException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.text.TextUtils; +import android.util.Log; + + +import java.io.FileNotFoundException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * A content provider for IM + */ +public class ImpsProvider extends ContentProvider { + private static final String LOG_TAG = "imProvider"; + private static final boolean DBG = false; + + private static final String AUTHORITY = "imps"; + + private static final String TABLE_ACCOUNTS = "accounts"; + private static final String TABLE_PROVIDERS = "providers"; + private static final String TABLE_PROVIDER_SETTINGS = "providerSettings"; + + private static final String TABLE_CONTACTS = "contacts"; + private static final String TABLE_CONTACTS_ETAG = "contactsEtag"; + private static final String TABLE_BLOCKED_LIST = "blockedList"; + private static final String TABLE_CONTACT_LIST = "contactList"; + private static final String TABLE_INVITATIONS = "invitations"; + private static final String TABLE_GROUP_MEMBERS = "groupMembers"; + private static final String TABLE_PRESENCE = "presence"; + private static final String USERNAME = "username"; + private static final String TABLE_CHATS = "chats"; + private static final String TABLE_AVATARS = "avatars"; + private static final String TABLE_SESSION_COOKIES = "sessionCookies"; + private static final String TABLE_MESSAGES = "messages"; + private static final String TABLE_IN_MEMORY_MESSAGES = "inMemoryMessages"; + private static final String TABLE_ACCOUNT_STATUS = "accountStatus"; + private static final String TABLE_BRANDING_RESOURCE_MAP_CACHE = "brandingResMapCache"; + + // tables for mcs and rmq + private static final String TABLE_OUTGOING_RMQ_MESSAGES = "outgoingRmqMessages"; + private static final String TABLE_LAST_RMQ_ID = "lastrmqid"; + private static final String TABLE_S2D_RMQ_IDS = "s2dRmqIds"; + + + private static final String DATABASE_NAME = "imps.db"; + private static final int DATABASE_VERSION = 1; + + protected static final int MATCH_PROVIDERS = 1; + protected static final int MATCH_PROVIDERS_BY_ID = 2; + protected static final int MATCH_PROVIDERS_WITH_ACCOUNT = 3; + protected static final int MATCH_ACCOUNTS = 10; + protected static final int MATCH_ACCOUNTS_BY_ID = 11; + protected static final int MATCH_CONTACTS = 18; + protected static final int MATCH_CONTACTS_JOIN_PRESENCE = 19; + protected static final int MATCH_CONTACTS_BAREBONE = 20; + protected static final int MATCH_CHATTING_CONTACTS = 21; + protected static final int MATCH_CONTACTS_BY_PROVIDER = 22; + protected static final int MATCH_CHATTING_CONTACTS_BY_PROVIDER = 23; + protected static final int MATCH_NO_CHATTING_CONTACTS_BY_PROVIDER = 24; + protected static final int MATCH_ONLINE_CONTACTS_BY_PROVIDER = 25; + protected static final int MATCH_OFFLINE_CONTACTS_BY_PROVIDER = 26; + protected static final int MATCH_CONTACT = 27; + protected static final int MATCH_CONTACTS_BULK = 28; + protected static final int MATCH_ONLINE_CONTACT_COUNT = 30; + protected static final int MATCH_BLOCKED_CONTACTS = 31; + protected static final int MATCH_CONTACTLISTS = 32; + protected static final int MATCH_CONTACTLISTS_BY_PROVIDER = 33; + protected static final int MATCH_CONTACTLIST = 34; + protected static final int MATCH_BLOCKEDLIST = 35; + protected static final int MATCH_BLOCKEDLIST_BY_PROVIDER = 36; + protected static final int MATCH_CONTACTS_ETAGS = 37; + protected static final int MATCH_CONTACTS_ETAG = 38; + protected static final int MATCH_PRESENCE = 40; + protected static final int MATCH_PRESENCE_ID = 41; + protected static final int MATCH_PRESENCE_BY_ACCOUNT = 42; + protected static final int MATCH_PRESENCE_SEED_BY_ACCOUNT = 43; + protected static final int MATCH_PRESENCE_BULK = 44; + + protected static final int MATCH_MESSAGES = 50; + protected static final int MATCH_MESSAGES_BY_CONTACT = 51; + protected static final int MATCH_MESSAGES_BY_THREAD_ID = 52; + protected static final int MATCH_MESSAGES_BY_PROVIDER = 53; + protected static final int MATCH_MESSAGES_BY_ACCOUNT = 54; + protected static final int MATCH_MESSAGE = 55; + protected static final int MATCH_OTR_MESSAGES = 56; + protected static final int MATCH_OTR_MESSAGES_BY_CONTACT = 57; + protected static final int MATCH_OTR_MESSAGES_BY_THREAD_ID = 58; + protected static final int MATCH_OTR_MESSAGES_BY_PROVIDER = 59; + protected static final int MATCH_OTR_MESSAGES_BY_ACCOUNT = 60; + protected static final int MATCH_OTR_MESSAGE = 61; + + protected static final int MATCH_GROUP_MEMBERS = 65; + protected static final int MATCH_GROUP_MEMBERS_BY_GROUP = 66; + protected static final int MATCH_AVATARS = 70; + protected static final int MATCH_AVATAR = 71; + protected static final int MATCH_AVATAR_BY_PROVIDER = 72; + protected static final int MATCH_CHATS = 80; + protected static final int MATCH_CHATS_BY_ACCOUNT = 81; + protected static final int MATCH_CHATS_ID = 82; + protected static final int MATCH_SESSIONS = 83; + protected static final int MATCH_SESSIONS_BY_PROVIDER = 84; + protected static final int MATCH_PROVIDER_SETTINGS = 90; + protected static final int MATCH_PROVIDER_SETTINGS_BY_ID = 91; + protected static final int MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME = 92; + protected static final int MATCH_INVITATIONS = 100; + protected static final int MATCH_INVITATION = 101; + protected static final int MATCH_ACCOUNTS_STATUS = 104; + protected static final int MATCH_ACCOUNT_STATUS = 105; + protected static final int MATCH_BRANDING_RESOURCE_MAP_CACHE = 106; + + // mcs url matcher + protected static final int MATCH_OUTGOING_RMQ_MESSAGES = 200; + protected static final int MATCH_OUTGOING_RMQ_MESSAGE = 201; + protected static final int MATCH_OUTGOING_HIGHEST_RMQ_ID = 202; + protected static final int MATCH_LAST_RMQ_ID = 203; + protected static final int MATCH_S2D_RMQ_IDS = 204; + + + protected final UriMatcher mUrlMatcher = new UriMatcher(UriMatcher.NO_MATCH); + private final String mTransientDbName; + + private static final HashMap sProviderAccountsProjectionMap; + private static final HashMap sContactsProjectionMap; + private static final HashMap sContactListProjectionMap; + private static final HashMap sBlockedListProjectionMap; + private static final HashMap sMessagesProjectionMap; + private static final HashMap sInMemoryMessagesProjectionMap; + + + private static final String PROVIDER_JOIN_ACCOUNT_TABLE = + "providers LEFT OUTER JOIN accounts ON " + + "(providers._id = accounts.provider AND accounts.active = 1) " + + "LEFT OUTER JOIN accountStatus ON (accounts._id = accountStatus.account)"; + + + private static final String CONTACT_JOIN_PRESENCE_TABLE = + "contacts LEFT OUTER JOIN presence ON (contacts._id = presence.contact_id)"; + + private static final String CONTACT_JOIN_PRESENCE_CHAT_TABLE = + CONTACT_JOIN_PRESENCE_TABLE + + " LEFT OUTER JOIN chats ON (contacts._id = chats.contact_id)"; + + private static final String CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE = + CONTACT_JOIN_PRESENCE_CHAT_TABLE + + " LEFT OUTER JOIN avatars ON (contacts.username = avatars.contact" + + " AND contacts.account = avatars.account_id)"; + + private static final String BLOCKEDLIST_JOIN_AVATAR_TABLE = + "blockedList LEFT OUTER JOIN avatars ON (blockedList.username = avatars.contact" + + " AND blockedList.account = avatars.account_id)"; + + private static final String MESSAGE_JOIN_CONTACT_TABLE = + "messages LEFT OUTER JOIN contacts ON (contacts._id = messages.thread_id)"; + + private static final String IN_MEMORY_MESSAGES_JOIN_CONTACT_TABLE = + "inMemoryMessages LEFT OUTER JOIN contacts ON " + + "(contacts._id = inMemoryMessages.thread_id)"; + + /** + * The where clause for filtering out blocked contacts + */ + private static final String NON_BLOCKED_CONTACTS_WHERE_CLAUSE = "(" + + Imps.Contacts.TYPE + " IS NULL OR " + + Imps.Contacts.TYPE + "!=" + + String.valueOf(Imps.Contacts.TYPE_BLOCKED) + + ")"; + + private static final String BLOCKED_CONTACTS_WHERE_CLAUSE = + "(contacts." + Imps.Contacts.TYPE + "=" + Imps.Contacts.TYPE_BLOCKED + ")"; + + private static final String CONTACT_ID = TABLE_CONTACTS + '.' + Imps.Contacts._ID; + private static final String PRESENCE_CONTACT_ID = TABLE_PRESENCE + '.' + Imps.Presence.CONTACT_ID; + + protected SQLiteOpenHelper mOpenHelper; + private final String mDatabaseName; + private final int mDatabaseVersion; + + private final String[] BACKFILL_PROJECTION = { + Imps.Chats._ID, Imps.Chats.SHORTCUT, Imps.Chats.LAST_MESSAGE_DATE + }; + + private final String[] FIND_SHORTCUT_PROJECTION = { + Imps.Chats._ID, Imps.Chats.SHORTCUT + }; + + // contact id query projection + private static final String[] CONTACT_ID_PROJECTION = new String[] { + Imps.Contacts._ID, // 0 + }; + private static final int CONTACT_ID_COLUMN = 0; + + // contact id query selection for "seed presence" operation + private static final String CONTACTS_WITH_NO_PRESENCE_SELECTION = + Imps.Contacts.ACCOUNT + "=?" + " AND " + Imps.Contacts._ID + + " in (select " + CONTACT_ID + " from " + TABLE_CONTACTS + + " left outer join " + TABLE_PRESENCE + " on " + CONTACT_ID + '=' + + PRESENCE_CONTACT_ID + " where " + PRESENCE_CONTACT_ID + " IS NULL)"; + + // contact id query selection args 1 + private String[] mQueryContactIdSelectionArgs1 = new String[1]; + + // contact id query selection for getContactId() + private static final String CONTACT_ID_QUERY_SELECTION = + Imps.Contacts.ACCOUNT + "=? AND " + Imps.Contacts.USERNAME + "=?"; + + // contact id query selection args 2 + private String[] mQueryContactIdSelectionArgs2 = new String[2]; + + + + private class DatabaseHelper extends SQLiteOpenHelper { + + DatabaseHelper(Context context) { + super(context, mDatabaseName, null, mDatabaseVersion); + } + + @Override + public void onCreate(SQLiteDatabase db) { + + if (DBG) log("DatabaseHelper.onCreate"); + + db.execSQL("CREATE TABLE " + TABLE_PROVIDERS + " (" + + "_id INTEGER PRIMARY KEY," + + "name TEXT," + // eg AIM + "fullname TEXT," + // eg AOL Instance Messenger + "category TEXT," + // a category used for forming intent + "signup_url TEXT" + // web url to visit to create a new account + ");"); + + db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " (" + + "_id INTEGER PRIMARY KEY," + + "name TEXT," + + "provider INTEGER," + + "username TEXT," + + "pw TEXT," + + "active INTEGER NOT NULL DEFAULT 0," + + "locked INTEGER NOT NULL DEFAULT 0," + + "keep_signed_in INTEGER NOT NULL DEFAULT 0," + + "last_login_state INTEGER NOT NULL DEFAULT 0," + + "UNIQUE (provider, username)" + + ");"); + + createContactsTables(db); + createMessageChatTables(db, true /* create show_ts column */); + + db.execSQL("CREATE TABLE " + TABLE_AVATARS + " (" + + "_id INTEGER PRIMARY KEY," + + "contact TEXT," + + "provider_id INTEGER," + + "account_id INTEGER," + + "hash TEXT," + + "data BLOB," + // raw image data + "UNIQUE (account_id, contact)" + + ");"); + + db.execSQL("CREATE TABLE " + TABLE_PROVIDER_SETTINGS + " (" + + "_id INTEGER PRIMARY KEY," + + "provider INTEGER," + + "name TEXT," + + "value TEXT," + + "UNIQUE (provider, name)" + + ");"); + + db.execSQL("create TABLE " + TABLE_BRANDING_RESOURCE_MAP_CACHE + " (" + + "_id INTEGER PRIMARY KEY," + + "provider_id INTEGER," + + "app_res_id INTEGER," + + "plugin_res_id INTEGER" + + ");"); + + // clean up account specific data when an account is deleted. + db.execSQL("CREATE TRIGGER account_cleanup " + + "DELETE ON " + TABLE_ACCOUNTS + + " BEGIN " + + "DELETE FROM " + TABLE_AVATARS + " WHERE account_id= OLD._id;" + + "END"); + + // add a database trigger to clean up associated provider settings + // while deleting a provider + db.execSQL("CREATE TRIGGER provider_cleanup " + + "DELETE ON " + TABLE_PROVIDERS + + " BEGIN " + + "DELETE FROM " + TABLE_PROVIDER_SETTINGS + " WHERE provider= OLD._id;" + + "END"); + + // the following are tables for mcs + db.execSQL("create TABLE " + TABLE_OUTGOING_RMQ_MESSAGES + " (" + + "_id INTEGER PRIMARY KEY," + + "rmq_id INTEGER," + + "type INTEGER," + + "ts INTEGER," + + "data TEXT" + + ");"); + + db.execSQL("create TABLE " + TABLE_LAST_RMQ_ID + " (" + + "_id INTEGER PRIMARY KEY," + + "rmq_id INTEGER" + + ");"); + + db.execSQL("create TABLE " + TABLE_S2D_RMQ_IDS + " (" + + "_id INTEGER PRIMARY KEY," + + "rmq_id INTEGER" + + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.d(LOG_TAG, "Upgrading database from version " + oldVersion + " to " + newVersion); + + switch (oldVersion) { + case 43: // this is the db version shipped in Dream 1.0 + // no-op: no schema changed from 43 to 44. The db version was changed to flush + // old provider settings, so new provider setting (including new name/value + // pairs) could be inserted by the plugins. + + // follow thru. + case 44: + if (newVersion <= 44) { + return; + } + + db.beginTransaction(); + try { + // add category column to the providers table + db.execSQL("ALTER TABLE " + TABLE_PROVIDERS + " ADD COLUMN category TEXT;"); + // add otr column to the contacts table + db.execSQL("ALTER TABLE " + TABLE_CONTACTS + " ADD COLUMN otr INTEGER;"); + + db.setTransactionSuccessful(); + } catch (Throwable ex) { + Log.e(LOG_TAG, ex.getMessage(), ex); + break; // force to destroy all old data; + } finally { + db.endTransaction(); + } + + case 45: + if (newVersion <= 45) { + return; + } + + db.beginTransaction(); + try { + // add an otr_etag column to contact etag table + db.execSQL( + "ALTER TABLE " + TABLE_CONTACTS_ETAG + " ADD COLUMN otr_etag TEXT;"); + db.setTransactionSuccessful(); + } catch (Throwable ex) { + Log.e(LOG_TAG, ex.getMessage(), ex); + break; // force to destroy all old data; + } finally { + db.endTransaction(); + } + + case 46: + if (newVersion <= 46) { + return; + } + + db.beginTransaction(); + try { + // add branding resource map cache table + db.execSQL("create TABLE " + TABLE_BRANDING_RESOURCE_MAP_CACHE + " (" + + "_id INTEGER PRIMARY KEY," + + "provider_id INTEGER," + + "app_res_id INTEGER," + + "plugin_res_id INTEGER" + + ");"); + db.setTransactionSuccessful(); + } catch (Throwable ex) { + Log.e(LOG_TAG, ex.getMessage(), ex); + break; // force to destroy all old data; + } finally { + db.endTransaction(); + } + + case 47: + if (newVersion <= 47) { + return; + } + + db.beginTransaction(); + try { + // when upgrading from version 47, don't create the show_ts column + // here. The upgrade step in 51 will add the show_ts column to the + // messages table. If we created the messages table with show_ts here, + // we'll get a duplicate column error later. + createMessageChatTables(db, false /* don't create show_ts column */); + db.setTransactionSuccessful(); + } catch (Throwable ex) { + Log.e(LOG_TAG, ex.getMessage(), ex); + break; // force to destroy all old data; + } finally { + db.endTransaction(); + } + + // fall thru. + + case 48: + case 49: + case 50: + if (newVersion <= 50) { + return; + } + + db.beginTransaction(); + try { + // add rmq2 s2d ids table + db.execSQL("create TABLE " + TABLE_S2D_RMQ_IDS + " (" + + "_id INTEGER PRIMARY KEY," + + "rmq_id INTEGER" + + ");"); + db.setTransactionSuccessful(); + } catch (Throwable ex) { + Log.e(LOG_TAG, ex.getMessage(), ex); + break; // force to destroy all old data; + } finally { + db.endTransaction(); + } + + case 51: + if (newVersion <= 51) { + return; + } + + db.beginTransaction(); + try { + db.execSQL( + "ALTER TABLE " + TABLE_MESSAGES + " ADD COLUMN show_ts INTEGER;"); + db.setTransactionSuccessful(); + } catch (Throwable ex) { + Log.e(LOG_TAG, ex.getMessage(), ex); + break; // force to destroy all old data; + } finally { + db.endTransaction(); + } + + return; + } + + Log.w(LOG_TAG, "Couldn't upgrade db to " + newVersion + ". Destroying old data."); + destroyOldTables(db); + onCreate(db); + } + + private void destroyOldTables(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_PROVIDERS); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_ACCOUNTS); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_CONTACT_LIST); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_BLOCKED_LIST); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_CONTACTS); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_CONTACTS_ETAG); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_AVATARS); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_PROVIDER_SETTINGS); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_BRANDING_RESOURCE_MAP_CACHE); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_MESSAGES); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_CHATS); + + // mcs/rmq stuff + db.execSQL("DROP TABLE IF EXISTS " + TABLE_OUTGOING_RMQ_MESSAGES); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_LAST_RMQ_ID); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_S2D_RMQ_IDS); + } + + private void createContactsTables(SQLiteDatabase db) { + if (DBG) log("createContactsTables"); + + StringBuilder buf = new StringBuilder(); + String contactsTableName = TABLE_CONTACTS; + + // creating the "contacts" table + buf.append("CREATE TABLE IF NOT EXISTS "); + buf.append(contactsTableName); + buf.append(" ("); + buf.append("_id INTEGER PRIMARY KEY,"); + buf.append("username TEXT,"); + buf.append("nickname TEXT,"); + + buf.append("provider INTEGER,"); + buf.append("account INTEGER,"); + buf.append("contactList INTEGER,"); + buf.append("type INTEGER,"); + buf.append("subscriptionStatus INTEGER,"); + buf.append("subscriptionType INTEGER,"); + + // the following are derived from Google Contact Extension, we don't include all + // the attributes, just the ones we can use. + // (see http://code.google.com/apis/talk/jep_extensions/roster_attributes.html) + // + // qc: quick contact (derived from message count) + // rejected: if the contact has ever been rejected by the user + buf.append("qc INTEGER,"); + buf.append("rejected INTEGER,"); + + // Off the record status + buf.append("otr INTEGER"); + + buf.append(");"); + + db.execSQL(buf.toString()); + + buf.delete(0, buf.length()); + + // creating contact etag table + buf.append("CREATE TABLE IF NOT EXISTS "); + buf.append(TABLE_CONTACTS_ETAG); + buf.append(" ("); + buf.append("_id INTEGER PRIMARY KEY,"); + buf.append("etag TEXT,"); + buf.append("otr_etag TEXT,"); + buf.append("account INTEGER UNIQUE"); + buf.append(");"); + + db.execSQL(buf.toString()); + + buf.delete(0, buf.length()); + + // creating the "contactList" table + buf.append("CREATE TABLE IF NOT EXISTS "); + buf.append(TABLE_CONTACT_LIST); + buf.append(" ("); + buf.append("_id INTEGER PRIMARY KEY,"); + buf.append("name TEXT,"); + buf.append("provider INTEGER,"); + buf.append("account INTEGER"); + buf.append(");"); + + db.execSQL(buf.toString()); + + buf.delete(0, buf.length()); + + // creating the "blockedList" table + buf.append("CREATE TABLE IF NOT EXISTS "); + buf.append(TABLE_BLOCKED_LIST); + buf.append(" ("); + buf.append("_id INTEGER PRIMARY KEY,"); + buf.append("username TEXT,"); + buf.append("nickname TEXT,"); + buf.append("provider INTEGER,"); + buf.append("account INTEGER"); + buf.append(");"); + + db.execSQL(buf.toString()); + } + + private void createMessageChatTables(SQLiteDatabase db, + boolean addShowTsColumnForMessagesTable) { + if (DBG) log("createMessageChatTables"); + + // message table + StringBuilder buf = new StringBuilder(); + buf.append("CREATE TABLE IF NOT EXISTS "); + buf.append(TABLE_MESSAGES); + buf.append(" (_id INTEGER PRIMARY KEY,"); + buf.append("thread_id INTEGER,"); + buf.append("nickname TEXT,"); + buf.append("body TEXT,"); + buf.append("date INTEGER,"); + buf.append("type INTEGER,"); + buf.append("packet_id TEXT UNIQUE,"); + buf.append("err_code INTEGER NOT NULL DEFAULT 0,"); + buf.append("err_msg TEXT,"); + buf.append("is_muc INTEGER"); + + if (addShowTsColumnForMessagesTable) { + buf.append(",show_ts INTEGER"); + } + + buf.append(");"); + + String sqlStatement = buf.toString(); + + if (DBG) log("create message table: " + sqlStatement); + db.execSQL(sqlStatement); + + buf.delete(0, buf.length()); + buf.append("CREATE TABLE IF NOT EXISTS "); + buf.append(TABLE_CHATS); + buf.append(" (_id INTEGER PRIMARY KEY,"); + buf.append("contact_id INTEGER UNIQUE,"); + buf.append("jid_resource TEXT,"); // the JID resource for the user, for non-group chats + buf.append("groupchat INTEGER,"); // 1 if group chat, 0 if not TODO: remove this column + buf.append("last_unread_message TEXT,"); // the last unread message + buf.append("last_message_date INTEGER,"); // in seconds + buf.append("unsent_composed_message TEXT,"); // a composed, but not sent message + buf.append("shortcut INTEGER);"); // which of 10 slots (if any) this chat occupies + + // chat sessions, including single person chats and group chats + sqlStatement = buf.toString(); + + if (DBG) log("create chat table: " + sqlStatement); + db.execSQL(sqlStatement); + + buf.delete(0, buf.length()); + buf.append("CREATE TRIGGER IF NOT EXISTS contact_cleanup "); + buf.append("DELETE ON contacts "); + buf.append("BEGIN "); + buf.append("DELETE FROM ").append(TABLE_CHATS).append(" WHERE contact_id = OLD._id;"); + buf.append("DELETE FROM ").append(TABLE_MESSAGES).append(" WHERE thread_id = OLD._id;"); + buf.append("END"); + + sqlStatement = buf.toString(); + + if (DBG) log("create trigger: " + sqlStatement); + db.execSQL(sqlStatement); + } + + private void createInMemoryMessageTables(SQLiteDatabase db, String tablePrefix) { + String tableName = (tablePrefix != null) ? + tablePrefix+TABLE_IN_MEMORY_MESSAGES : TABLE_IN_MEMORY_MESSAGES; + + db.execSQL("CREATE TABLE IF NOT EXISTS " + tableName + " (" + + "_id INTEGER PRIMARY KEY," + + "thread_id INTEGER," + + "nickname TEXT," + + "body TEXT," + + "date INTEGER," + // in millisec + "type INTEGER," + + "packet_id TEXT UNIQUE," + + "err_code INTEGER NOT NULL DEFAULT 0," + + "err_msg TEXT," + + "is_muc INTEGER," + + "show_ts INTEGER" + + ");"); + + } + + @Override + public void onOpen(SQLiteDatabase db) { + if (db.isReadOnly()) { + Log.w(LOG_TAG, "ImProvider database opened in read only mode."); + Log.w(LOG_TAG, "Transient tables not created."); + return; + } + + if (DBG) log("##### createTransientTables"); + + // Create transient tables + String cpDbName; + db.execSQL("ATTACH DATABASE ':memory:' AS " + mTransientDbName + ";"); + cpDbName = mTransientDbName + "."; + + // in-memory message table + createInMemoryMessageTables(db, cpDbName); + + // presence + db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + TABLE_PRESENCE + " ("+ + "_id INTEGER PRIMARY KEY," + + "contact_id INTEGER UNIQUE," + + "jid_resource TEXT," + // jid resource for the presence + "client_type INTEGER," + // client type + "priority INTEGER," + // presence priority (XMPP) + "mode INTEGER," + // presence mode + "status TEXT" + // custom status + ");"); + + // group chat invitations + db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + TABLE_INVITATIONS + " (" + + "_id INTEGER PRIMARY KEY," + + "providerId INTEGER," + + "accountId INTEGER," + + "inviteId TEXT," + + "sender TEXT," + + "groupName TEXT," + + "note TEXT," + + "status INTEGER" + + ");"); + + // group chat members + db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + TABLE_GROUP_MEMBERS + " (" + + "_id INTEGER PRIMARY KEY," + + "groupId INTEGER," + + "username TEXT," + + "nickname TEXT" + + ");"); + + db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + TABLE_ACCOUNT_STATUS + " (" + + "_id INTEGER PRIMARY KEY," + + "account INTEGER UNIQUE," + + "presenceStatus INTEGER," + + "connStatus INTEGER" + + ");" + ); + + /* when we moved the contact table out of transient_db and into the main db, the + presence and groupchat cleanup triggers don't work anymore. It seems we can't + create triggers that reference objects in a different database! + + // Insert a default presence for newly inserted contact + db.execSQL("CREATE TRIGGER IF NOT EXISTS contact_create_presence " + + "AFTER INSERT ON " + contactsTableName + + " WHEN NEW.type != " + Im.Contacts.TYPE_GROUP + + " BEGIN " + + "INSERT INTO presence (contact_id) VALUES (NEW._id);" + + " END"); + + // Remove the presence when the contact is removed. + db.execSQL("CREATE TRIGGER IF NOT EXISTS contact_presence_cleanup " + + "DELETE ON " + contactsTableName + + " BEGIN " + + "DELETE FROM presence WHERE contact_id = OLD._id;" + + "END"); + + // Cleans up group members and group messages when a group chat is deleted + db.execSQL("CREATE TRIGGER IF NOT EXISTS " + cpDbName + "group_cleanup " + + "DELETE ON " + cpDbName + contactsTableName + + " FOR EACH ROW WHEN OLD.type = " + Im.Contacts.TYPE_GROUP + + " BEGIN " + + "DELETE FROM groupMembers WHERE groupId = OLD._id;" + + "DELETE FROM groupMessages WHERE groupId = OLD._id;" + + " END"); + */ + + // only store the session cookies in memory right now. This means + // that we don't persist them across device reboot + db.execSQL("CREATE TABLE IF NOT EXISTS " + cpDbName + TABLE_SESSION_COOKIES + " ("+ + "_id INTEGER PRIMARY KEY," + + "provider INTEGER," + + "account INTEGER," + + "name TEXT," + + "value TEXT" + + ");"); + + } + } + + static { + sProviderAccountsProjectionMap = new HashMap(); + sProviderAccountsProjectionMap.put(Imps.Provider._ID, + "providers._id AS _id"); + sProviderAccountsProjectionMap.put(Imps.Provider._COUNT, + "COUNT(*) AS _account"); + sProviderAccountsProjectionMap.put(Imps.Provider.NAME, + "providers.name AS name"); + sProviderAccountsProjectionMap.put(Imps.Provider.FULLNAME, + "providers.fullname AS fullname"); + sProviderAccountsProjectionMap.put(Imps.Provider.CATEGORY, + "providers.category AS category"); + sProviderAccountsProjectionMap.put(Imps.Provider.ACTIVE_ACCOUNT_ID, + "accounts._id AS account_id"); + sProviderAccountsProjectionMap.put(Imps.Provider.ACTIVE_ACCOUNT_USERNAME, + "accounts.username AS account_username"); + sProviderAccountsProjectionMap.put(Imps.Provider.ACTIVE_ACCOUNT_PW, + "accounts.pw AS account_pw"); + sProviderAccountsProjectionMap.put(Imps.Provider.ACTIVE_ACCOUNT_LOCKED, + "accounts.locked AS account_locked"); + sProviderAccountsProjectionMap.put(Imps.Provider.ACTIVE_ACCOUNT_KEEP_SIGNED_IN, + "accounts.keep_signed_in AS account_keepSignedIn"); + sProviderAccountsProjectionMap.put(Imps.Provider.ACCOUNT_PRESENCE_STATUS, + "accountStatus.presenceStatus AS account_presenceStatus"); + sProviderAccountsProjectionMap.put(Imps.Provider.ACCOUNT_CONNECTION_STATUS, + "accountStatus.connStatus AS account_connStatus"); + + // contacts projection map + sContactsProjectionMap = new HashMap(); + + // Base column + sContactsProjectionMap.put(Imps.Contacts._ID, "contacts._id AS _id"); + sContactsProjectionMap.put(Imps.Contacts._COUNT, "COUNT(*) AS _count"); + + // contacts column + sContactsProjectionMap.put(Imps.Contacts._ID, "contacts._id as _id"); + sContactsProjectionMap.put(Imps.Contacts.USERNAME, "contacts.username as username"); + sContactsProjectionMap.put(Imps.Contacts.NICKNAME, "contacts.nickname as nickname"); + sContactsProjectionMap.put(Imps.Contacts.PROVIDER, "contacts.provider as provider"); + sContactsProjectionMap.put(Imps.Contacts.ACCOUNT, "contacts.account as account"); + sContactsProjectionMap.put(Imps.Contacts.CONTACTLIST, "contacts.contactList as contactList"); + sContactsProjectionMap.put(Imps.Contacts.TYPE, "contacts.type as type"); + sContactsProjectionMap.put(Imps.Contacts.SUBSCRIPTION_STATUS, + "contacts.subscriptionStatus as subscriptionStatus"); + sContactsProjectionMap.put(Imps.Contacts.SUBSCRIPTION_TYPE, + "contacts.subscriptionType as subscriptionType"); + sContactsProjectionMap.put(Imps.Contacts.QUICK_CONTACT, "contacts.qc as qc"); + sContactsProjectionMap.put(Imps.Contacts.REJECTED, "contacts.rejected as rejected"); + + // Presence columns + sContactsProjectionMap.put(Imps.Presence.CONTACT_ID, + "presence.contact_id AS contact_id"); + sContactsProjectionMap.put(Imps.Contacts.PRESENCE_STATUS, + "presence.mode AS mode"); + sContactsProjectionMap.put(Imps.Contacts.PRESENCE_CUSTOM_STATUS, + "presence.status AS status"); + sContactsProjectionMap.put(Imps.Contacts.CLIENT_TYPE, + "presence.client_type AS client_type"); + + // Chats columns + sContactsProjectionMap.put(Imps.Contacts.CHATS_CONTACT, + "chats.contact_id AS chats_contact_id"); + sContactsProjectionMap.put(Imps.Chats.JID_RESOURCE, + "chats.jid_resource AS jid_resource"); + sContactsProjectionMap.put(Imps.Chats.GROUP_CHAT, + "chats.groupchat AS groupchat"); + sContactsProjectionMap.put(Imps.Contacts.LAST_UNREAD_MESSAGE, + "chats.last_unread_message AS last_unread_message"); + sContactsProjectionMap.put(Imps.Contacts.LAST_MESSAGE_DATE, + "chats.last_message_date AS last_message_date"); + sContactsProjectionMap.put(Imps.Contacts.UNSENT_COMPOSED_MESSAGE, + "chats.unsent_composed_message AS unsent_composed_message"); + sContactsProjectionMap.put(Imps.Contacts.SHORTCUT, "chats.SHORTCUT AS shortcut"); + + // Avatars columns + sContactsProjectionMap.put(Imps.Contacts.AVATAR_HASH, "avatars.hash AS avatars_hash"); + sContactsProjectionMap.put(Imps.Contacts.AVATAR_DATA, "avatars.data AS avatars_data"); + + // contactList projection map + sContactListProjectionMap = new HashMap(); + sContactListProjectionMap.put(Imps.ContactList._ID, "contactList._id AS _id"); + sContactListProjectionMap.put(Imps.ContactList._COUNT, "COUNT(*) AS _count"); + sContactListProjectionMap.put(Imps.ContactList.NAME, "name"); + sContactListProjectionMap.put(Imps.ContactList.PROVIDER, "provider"); + sContactListProjectionMap.put(Imps.ContactList.ACCOUNT, "account"); + + // blockedList projection map + sBlockedListProjectionMap = new HashMap(); + sBlockedListProjectionMap.put(Imps.BlockedList._ID, "blockedList._id AS _id"); + sBlockedListProjectionMap.put(Imps.BlockedList._COUNT, "COUNT(*) AS _count"); + sBlockedListProjectionMap.put(Imps.BlockedList.USERNAME, "username"); + sBlockedListProjectionMap.put(Imps.BlockedList.NICKNAME, "nickname"); + sBlockedListProjectionMap.put(Imps.BlockedList.PROVIDER, "provider"); + sBlockedListProjectionMap.put(Imps.BlockedList.ACCOUNT, "account"); + sBlockedListProjectionMap.put(Imps.BlockedList.AVATAR_DATA, + "avatars.data AS avatars_data"); + + // messages projection map + sMessagesProjectionMap = new HashMap(); + sMessagesProjectionMap.put(Imps.Messages._ID, "messages._id AS _id"); + sMessagesProjectionMap.put(Imps.Messages._COUNT, "COUNT(*) AS _count"); + sMessagesProjectionMap.put(Imps.Messages.THREAD_ID, "messages.thread_id AS thread_id"); + sMessagesProjectionMap.put(Imps.Messages.PACKET_ID, "messages.packet_id AS packet_id"); + sMessagesProjectionMap.put(Imps.Messages.NICKNAME, "messages.nickname AS nickname"); + sMessagesProjectionMap.put(Imps.Messages.BODY, "messages.body AS body"); + sMessagesProjectionMap.put(Imps.Messages.DATE, "messages.date AS date"); + sMessagesProjectionMap.put(Imps.Messages.TYPE, "messages.type AS type"); + sMessagesProjectionMap.put(Imps.Messages.ERROR_CODE, "messages.err_code AS err_code"); + sMessagesProjectionMap.put(Imps.Messages.ERROR_MESSAGE, "messages.err_msg AS err_msg"); + sMessagesProjectionMap.put(Imps.Messages.IS_GROUP_CHAT, "messages.is_muc AS is_muc"); + sMessagesProjectionMap.put(Imps.Messages.DISPLAY_SENT_TIME, "messages.show_ts AS show_ts"); + // contacts columns + sMessagesProjectionMap.put(Imps.Messages.CONTACT, "contacts.username AS contact"); + sMessagesProjectionMap.put(Imps.Contacts.PROVIDER, "contacts.provider AS provider"); + sMessagesProjectionMap.put(Imps.Contacts.ACCOUNT, "contacts.account AS account"); + sMessagesProjectionMap.put("contact_type", "contacts.type AS contact_type"); + + sInMemoryMessagesProjectionMap = new HashMap(); + sInMemoryMessagesProjectionMap.put(Imps.Messages._ID, + "inMemoryMessages._id AS _id"); + sInMemoryMessagesProjectionMap.put(Imps.Messages._COUNT, + "COUNT(*) AS _count"); + sInMemoryMessagesProjectionMap.put(Imps.Messages.THREAD_ID, + "inMemoryMessages.thread_id AS thread_id"); + sInMemoryMessagesProjectionMap.put(Imps.Messages.PACKET_ID, + "inMemoryMessages.packet_id AS packet_id"); + sInMemoryMessagesProjectionMap.put(Imps.Messages.NICKNAME, + "inMemoryMessages.nickname AS nickname"); + sInMemoryMessagesProjectionMap.put(Imps.Messages.BODY, + "inMemoryMessages.body AS body"); + sInMemoryMessagesProjectionMap.put(Imps.Messages.DATE, + "inMemoryMessages.date AS date"); + sInMemoryMessagesProjectionMap.put(Imps.Messages.TYPE, + "inMemoryMessages.type AS type"); + sInMemoryMessagesProjectionMap.put(Imps.Messages.ERROR_CODE, + "inMemoryMessages.err_code AS err_code"); + sInMemoryMessagesProjectionMap.put(Imps.Messages.ERROR_MESSAGE, + "inMemoryMessages.err_msg AS err_msg"); + sInMemoryMessagesProjectionMap.put(Imps.Messages.IS_GROUP_CHAT, + "inMemoryMessages.is_muc AS is_muc"); + sInMemoryMessagesProjectionMap.put(Imps.Messages.DISPLAY_SENT_TIME, + "inMemoryMessages.show_ts AS show_ts"); + // contacts columns + sInMemoryMessagesProjectionMap.put(Imps.Messages.CONTACT, "contacts.username AS contact"); + sInMemoryMessagesProjectionMap.put(Imps.Contacts.PROVIDER, "contacts.provider AS provider"); + sInMemoryMessagesProjectionMap.put(Imps.Contacts.ACCOUNT, "contacts.account AS account"); + sInMemoryMessagesProjectionMap.put("contact_type", "contacts.type AS contact_type"); + } + + public ImpsProvider() { + this(DATABASE_NAME, DATABASE_VERSION); + + setupImUrlMatchers(AUTHORITY); + setupMcsUrlMatchers(AUTHORITY); + } + + protected ImpsProvider(String dbName, int dbVersion) { + mDatabaseName = dbName; + mDatabaseVersion = dbVersion; + mTransientDbName = "transient_" + dbName.replace(".", "_"); + } + + private void setupImUrlMatchers(String authority) { + mUrlMatcher.addURI(authority, "providers", MATCH_PROVIDERS); + mUrlMatcher.addURI(authority, "providers/#", MATCH_PROVIDERS_BY_ID); + mUrlMatcher.addURI(authority, "providers/account", MATCH_PROVIDERS_WITH_ACCOUNT); + + mUrlMatcher.addURI(authority, "accounts", MATCH_ACCOUNTS); + mUrlMatcher.addURI(authority, "accounts/#", MATCH_ACCOUNTS_BY_ID); + + mUrlMatcher.addURI(authority, "contacts", MATCH_CONTACTS); + mUrlMatcher.addURI(authority, "contactsWithPresence", MATCH_CONTACTS_JOIN_PRESENCE); + mUrlMatcher.addURI(authority, "contactsBarebone", MATCH_CONTACTS_BAREBONE); + mUrlMatcher.addURI(authority, "contacts/#/#", MATCH_CONTACTS_BY_PROVIDER); + mUrlMatcher.addURI(authority, "contacts/chatting", MATCH_CHATTING_CONTACTS); + mUrlMatcher.addURI(authority, "contacts/chatting/#/#", MATCH_CHATTING_CONTACTS_BY_PROVIDER); + mUrlMatcher.addURI(authority, "contacts/online/#/#", MATCH_ONLINE_CONTACTS_BY_PROVIDER); + mUrlMatcher.addURI(authority, "contacts/offline/#/#", MATCH_OFFLINE_CONTACTS_BY_PROVIDER); + mUrlMatcher.addURI(authority, "contacts/#", MATCH_CONTACT); + mUrlMatcher.addURI(authority, "contacts/blocked", MATCH_BLOCKED_CONTACTS); + mUrlMatcher.addURI(authority, "bulk_contacts", MATCH_CONTACTS_BULK); + mUrlMatcher.addURI(authority, "contacts/onlineCount", MATCH_ONLINE_CONTACT_COUNT); + + mUrlMatcher.addURI(authority, "contactLists", MATCH_CONTACTLISTS); + mUrlMatcher.addURI(authority, "contactLists/#/#", MATCH_CONTACTLISTS_BY_PROVIDER); + mUrlMatcher.addURI(authority, "contactLists/#", MATCH_CONTACTLIST); + mUrlMatcher.addURI(authority, "blockedList", MATCH_BLOCKEDLIST); + mUrlMatcher.addURI(authority, "blockedList/#/#", MATCH_BLOCKEDLIST_BY_PROVIDER); + + mUrlMatcher.addURI(authority, "contactsEtag", MATCH_CONTACTS_ETAGS); + mUrlMatcher.addURI(authority, "contactsEtag/#", MATCH_CONTACTS_ETAG); + + mUrlMatcher.addURI(authority, "presence", MATCH_PRESENCE); + mUrlMatcher.addURI(authority, "presence/#", MATCH_PRESENCE_ID); + mUrlMatcher.addURI(authority, "presence/account/#", MATCH_PRESENCE_BY_ACCOUNT); + mUrlMatcher.addURI(authority, "seed_presence/account/#", MATCH_PRESENCE_SEED_BY_ACCOUNT); + mUrlMatcher.addURI(authority, "bulk_presence", MATCH_PRESENCE_BULK); + + mUrlMatcher.addURI(authority, "messages", MATCH_MESSAGES); + mUrlMatcher.addURI(authority, "messagesByAcctAndContact/#/*", MATCH_MESSAGES_BY_CONTACT); + mUrlMatcher.addURI(authority, "messagesByThreadId/#", MATCH_MESSAGES_BY_THREAD_ID); + mUrlMatcher.addURI(authority, "messagesByProvider/#", MATCH_MESSAGES_BY_PROVIDER); + mUrlMatcher.addURI(authority, "messagesByAccount/#", MATCH_MESSAGES_BY_ACCOUNT); + mUrlMatcher.addURI(authority, "messages/#", MATCH_MESSAGE); + + mUrlMatcher.addURI(authority, "otrMessages", MATCH_OTR_MESSAGES); + mUrlMatcher.addURI(authority, "otrMessagesByAcctAndContact/#/*", + MATCH_OTR_MESSAGES_BY_CONTACT); + mUrlMatcher.addURI(authority, "otrMessagesByThreadId/#", MATCH_OTR_MESSAGES_BY_THREAD_ID); + mUrlMatcher.addURI(authority, "otrMessagesByProvider/#", MATCH_OTR_MESSAGES_BY_PROVIDER); + mUrlMatcher.addURI(authority, "otrMessagesByAccount/#", MATCH_OTR_MESSAGES_BY_ACCOUNT); + mUrlMatcher.addURI(authority, "otrMessages/#", MATCH_OTR_MESSAGE); + + mUrlMatcher.addURI(authority, "groupMembers", MATCH_GROUP_MEMBERS); + mUrlMatcher.addURI(authority, "groupMembers/#", MATCH_GROUP_MEMBERS_BY_GROUP); + + mUrlMatcher.addURI(authority, "avatars", MATCH_AVATARS); + mUrlMatcher.addURI(authority, "avatars/#", MATCH_AVATAR); + mUrlMatcher.addURI(authority, "avatarsBy/#/#", MATCH_AVATAR_BY_PROVIDER); + mUrlMatcher.addURI(authority, "chats", MATCH_CHATS); + mUrlMatcher.addURI(authority, "chats/account/#", MATCH_CHATS_BY_ACCOUNT); + mUrlMatcher.addURI(authority, "chats/#", MATCH_CHATS_ID); + + mUrlMatcher.addURI(authority, "sessionCookies", MATCH_SESSIONS); + mUrlMatcher.addURI(authority, "sessionCookiesBy/#/#", MATCH_SESSIONS_BY_PROVIDER); + mUrlMatcher.addURI(authority, "providerSettings", MATCH_PROVIDER_SETTINGS); + mUrlMatcher.addURI(authority, "providerSettings/#", MATCH_PROVIDER_SETTINGS_BY_ID); + mUrlMatcher.addURI(authority, "providerSettings/#/*", + MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME); + + mUrlMatcher.addURI(authority, "invitations", MATCH_INVITATIONS); + mUrlMatcher.addURI(authority, "invitations/#", MATCH_INVITATION); + + mUrlMatcher.addURI(authority, "accountStatus", MATCH_ACCOUNTS_STATUS); + mUrlMatcher.addURI(authority, "accountStatus/#", MATCH_ACCOUNT_STATUS); + + mUrlMatcher.addURI(authority, "brandingResMapCache", MATCH_BRANDING_RESOURCE_MAP_CACHE); + } + + private void setupMcsUrlMatchers(String authority) { + mUrlMatcher.addURI(authority, "outgoingRmqMessages", MATCH_OUTGOING_RMQ_MESSAGES); + mUrlMatcher.addURI(authority, "outgoingRmqMessages/#", MATCH_OUTGOING_RMQ_MESSAGE); + mUrlMatcher.addURI(authority, "outgoingHighestRmqId", MATCH_OUTGOING_HIGHEST_RMQ_ID); + mUrlMatcher.addURI(authority, "lastRmqId", MATCH_LAST_RMQ_ID); + mUrlMatcher.addURI(authority, "s2dids", MATCH_S2D_RMQ_IDS); + } + + @Override + public boolean onCreate() { + mOpenHelper = new DatabaseHelper(getContext()); + return true; + } + + @Override + public final int update(final Uri url, final ContentValues values, + final String selection, final String[] selectionArgs) { + + int result = 0; + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + try { + result = updateInternal(url, values, selection, selectionArgs); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + if (result > 0) { + getContext().getContentResolver() + .notifyChange(url, null /* observer */, false /* sync */); + } + return result; + } + + @Override + public final int delete(final Uri url, final String selection, + final String[] selectionArgs) { + int result; + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + try { + result = deleteInternal(url, selection, selectionArgs); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + if (result > 0) { + getContext().getContentResolver() + .notifyChange(url, null /* observer */, false /* sync */); + } + return result; + } + + @Override + public final Uri insert(final Uri url, final ContentValues values) { + Uri result; + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + try { + result = insertInternal(url, values); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + if (result != null) { + getContext().getContentResolver() + .notifyChange(url, null /* observer */, false /* sync */); + } + return result; + } + + @Override + public final Cursor query(final Uri url, final String[] projection, + final String selection, final String[] selectionArgs, + final String sortOrder) { + return queryInternal(url, projection, selection, selectionArgs, sortOrder); + } + + public Cursor queryInternal(Uri url, String[] projectionIn, + String selection, String[] selectionArgs, String sort) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + StringBuilder whereClause = new StringBuilder(); + if(selection != null) { + whereClause.append(selection); + } + String groupBy = null; + String limit = null; + + // Generate the body of the query + int match = mUrlMatcher.match(url); + + if (DBG) { + log("query " + url + ", match " + match + ", where " + selection); + if (selectionArgs != null) { + for (String selectionArg : selectionArgs) { + log(" selectionArg: " + selectionArg); + } + } + } + + switch (match) { + case MATCH_PROVIDERS_BY_ID: + appendWhere(whereClause, Imps.Provider._ID, "=", url.getPathSegments().get(1)); + // fall thru. + + case MATCH_PROVIDERS: + qb.setTables(TABLE_PROVIDERS); + break; + + case MATCH_PROVIDERS_WITH_ACCOUNT: + qb.setTables(PROVIDER_JOIN_ACCOUNT_TABLE); + qb.setProjectionMap(sProviderAccountsProjectionMap); + break; + + case MATCH_ACCOUNTS_BY_ID: + appendWhere(whereClause, Imps.Account._ID, "=", url.getPathSegments().get(1)); + // falls down + case MATCH_ACCOUNTS: + qb.setTables(TABLE_ACCOUNTS); + break; + + case MATCH_CONTACTS: + qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE); + qb.setProjectionMap(sContactsProjectionMap); + break; + + case MATCH_CONTACTS_JOIN_PRESENCE: + qb.setTables(CONTACT_JOIN_PRESENCE_TABLE); + qb.setProjectionMap(sContactsProjectionMap); + break; + + case MATCH_CONTACTS_BAREBONE: + qb.setTables(TABLE_CONTACTS); + break; + + case MATCH_CHATTING_CONTACTS: + qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE); + qb.setProjectionMap(sContactsProjectionMap); + appendWhere(whereClause, "chats.last_message_date IS NOT NULL"); + // no need to add the non blocked contacts clause because + // blocked contacts can't have conversations. + break; + + case MATCH_CONTACTS_BY_PROVIDER: + buildQueryContactsByProvider(qb, whereClause, url); + appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE); + break; + + case MATCH_CHATTING_CONTACTS_BY_PROVIDER: + buildQueryContactsByProvider(qb, whereClause, url); + appendWhere(whereClause, "chats.last_message_date IS NOT NULL"); + // no need to add the non blocked contacts clause because + // blocked contacts can't have conversations. + break; + + case MATCH_NO_CHATTING_CONTACTS_BY_PROVIDER: + buildQueryContactsByProvider(qb, whereClause, url); + appendWhere(whereClause, "chats.last_message_date IS NULL"); + appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE); + break; + + case MATCH_ONLINE_CONTACTS_BY_PROVIDER: + buildQueryContactsByProvider(qb, whereClause, url); + appendWhere(whereClause, Imps.Contacts.PRESENCE_STATUS, "!=", Imps.Presence.OFFLINE); + appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE); + break; + + case MATCH_OFFLINE_CONTACTS_BY_PROVIDER: + buildQueryContactsByProvider(qb, whereClause, url); + appendWhere(whereClause, Imps.Contacts.PRESENCE_STATUS, "=", Imps.Presence.OFFLINE); + appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE); + break; + + case MATCH_BLOCKED_CONTACTS: + qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE); + qb.setProjectionMap(sContactsProjectionMap); + appendWhere(whereClause, BLOCKED_CONTACTS_WHERE_CLAUSE); + break; + + case MATCH_CONTACT: + qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE); + qb.setProjectionMap(sContactsProjectionMap); + appendWhere(whereClause, "contacts._id", "=", url.getPathSegments().get(1)); + break; + + case MATCH_ONLINE_CONTACT_COUNT: + qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_TABLE); + qb.setProjectionMap(sContactsProjectionMap); + appendWhere(whereClause, Imps.Contacts.PRESENCE_STATUS, "!=", Imps.Presence.OFFLINE); + appendWhere(whereClause, "chats.last_message_date IS NULL"); + appendWhere(whereClause, NON_BLOCKED_CONTACTS_WHERE_CLAUSE); + groupBy = Imps.Contacts.CONTACTLIST; + break; + + case MATCH_CONTACTLISTS_BY_PROVIDER: + appendWhere(whereClause, Imps.ContactList.ACCOUNT, "=", + url.getPathSegments().get(2)); + // fall through + case MATCH_CONTACTLISTS: + qb.setTables(TABLE_CONTACT_LIST); + qb.setProjectionMap(sContactListProjectionMap); + break; + + case MATCH_CONTACTLIST: + qb.setTables(TABLE_CONTACT_LIST); + appendWhere(whereClause, Imps.ContactList._ID, "=", url.getPathSegments().get(1)); + break; + + case MATCH_BLOCKEDLIST: + qb.setTables(BLOCKEDLIST_JOIN_AVATAR_TABLE); + qb.setProjectionMap(sBlockedListProjectionMap); + break; + + case MATCH_BLOCKEDLIST_BY_PROVIDER: + qb.setTables(BLOCKEDLIST_JOIN_AVATAR_TABLE); + qb.setProjectionMap(sBlockedListProjectionMap); + appendWhere(whereClause, Imps.BlockedList.ACCOUNT, "=", + url.getPathSegments().get(2)); + break; + + case MATCH_CONTACTS_ETAGS: + qb.setTables(TABLE_CONTACTS_ETAG); + break; + + case MATCH_CONTACTS_ETAG: + qb.setTables(TABLE_CONTACTS_ETAG); + appendWhere(whereClause, "_id", "=", url.getPathSegments().get(1)); + break; + + case MATCH_MESSAGES_BY_THREAD_ID: + appendWhere(whereClause, Imps.Messages.THREAD_ID, "=", url.getPathSegments().get(1)); + // fall thru. + + case MATCH_MESSAGES: + qb.setTables(TABLE_MESSAGES); + + final String selectionClause = whereClause.toString(); + final String query1 = qb.buildQuery(projectionIn, selectionClause, + null, null, null, null, null /* limit */); + + // Build the second query for frequent + qb = new SQLiteQueryBuilder(); + qb.setTables(TABLE_IN_MEMORY_MESSAGES); + final String query2 = qb.buildQuery(projectionIn, + selectionClause, null, null, null, null, null /* limit */); + + // Put them together + final String query = qb.buildUnionQuery(new String[] {query1, query2}, sort, null); + final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + Cursor c = db.rawQueryWithFactory(null, query, null, TABLE_MESSAGES); + if ((c != null) && !isTemporary()) { + c.setNotificationUri(getContext().getContentResolver(), url); + } + return c; + + case MATCH_MESSAGE: + qb.setTables(TABLE_MESSAGES); + appendWhere(whereClause, Imps.Messages._ID, "=", url.getPathSegments().get(1)); + break; + + case MATCH_MESSAGES_BY_CONTACT: + qb.setTables(MESSAGE_JOIN_CONTACT_TABLE); + qb.setProjectionMap(sMessagesProjectionMap); + + appendWhere(whereClause, Imps.Contacts.ACCOUNT, "=", url.getPathSegments().get(1)); + appendWhere(whereClause, "contacts.username", "=", + decodeURLSegment(url.getPathSegments().get(2))); + + final String sel = whereClause.toString(); + final String q1 = qb.buildQuery(projectionIn, sel, null, null, null, null, null); + + // Build the second query for frequent + qb = new SQLiteQueryBuilder(); + qb.setTables(IN_MEMORY_MESSAGES_JOIN_CONTACT_TABLE); + qb.setProjectionMap(sInMemoryMessagesProjectionMap); + final String q2 = qb.buildQuery(projectionIn, sel, null, null, null, null, null); + + // Put them together + final String q3 = qb.buildUnionQuery(new String[] {q1, q2}, sort, null); + final SQLiteDatabase db2 = mOpenHelper.getWritableDatabase(); + Cursor c2 = db2.rawQueryWithFactory(null, q3, null, MESSAGE_JOIN_CONTACT_TABLE); + if ((c2 != null) && !isTemporary()) { + c2.setNotificationUri(getContext().getContentResolver(), url); + } + return c2; + + case MATCH_INVITATIONS: + qb.setTables(TABLE_INVITATIONS); + break; + + case MATCH_INVITATION: + qb.setTables(TABLE_INVITATIONS); + appendWhere(whereClause, Imps.Invitation._ID, "=", url.getPathSegments().get(1)); + break; + + case MATCH_GROUP_MEMBERS: + qb.setTables(TABLE_GROUP_MEMBERS); + break; + + case MATCH_GROUP_MEMBERS_BY_GROUP: + qb.setTables(TABLE_GROUP_MEMBERS); + appendWhere(whereClause, Imps.GroupMembers.GROUP, "=", url.getPathSegments().get(1)); + break; + + case MATCH_AVATARS: + qb.setTables(TABLE_AVATARS); + break; + + case MATCH_AVATAR_BY_PROVIDER: + qb.setTables(TABLE_AVATARS); + appendWhere(whereClause, Imps.Avatars.ACCOUNT, "=", url.getPathSegments().get(2)); + break; + + case MATCH_CHATS: + qb.setTables(TABLE_CHATS); + break; + + case MATCH_CHATS_ID: + qb.setTables(TABLE_CHATS); + appendWhere(whereClause, Imps.Chats.CONTACT_ID, "=", url.getPathSegments().get(1)); + break; + + case MATCH_CHATS_BY_ACCOUNT: + qb.setTables(TABLE_CHATS); + String accountStr = decodeURLSegment(url.getLastPathSegment()); + appendWhere(whereClause, buildContactIdSelection(Imps.Chats.CONTACT_ID, + Imps.Contacts.ACCOUNT + "='" + accountStr + "'")); + break; + + case MATCH_PRESENCE: + qb.setTables(TABLE_PRESENCE); + break; + + case MATCH_PRESENCE_ID: + qb.setTables(TABLE_PRESENCE); + appendWhere(whereClause, Imps.Presence.CONTACT_ID, "=", url.getPathSegments().get(1)); + break; + + case MATCH_SESSIONS: + qb.setTables(TABLE_SESSION_COOKIES); + break; + + case MATCH_SESSIONS_BY_PROVIDER: + qb.setTables(TABLE_SESSION_COOKIES); + appendWhere(whereClause, Imps.SessionCookies.ACCOUNT, "=", url.getPathSegments().get(2)); + break; + + case MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME: + appendWhere(whereClause, Imps.ProviderSettings.NAME, "=", url.getPathSegments().get(2)); + // fall through + case MATCH_PROVIDER_SETTINGS_BY_ID: + appendWhere(whereClause, Imps.ProviderSettings.PROVIDER, "=", url.getPathSegments().get(1)); + // fall through + case MATCH_PROVIDER_SETTINGS: + qb.setTables(TABLE_PROVIDER_SETTINGS); + break; + + case MATCH_ACCOUNTS_STATUS: + qb.setTables(TABLE_ACCOUNT_STATUS); + break; + + case MATCH_ACCOUNT_STATUS: + qb.setTables(TABLE_ACCOUNT_STATUS); + appendWhere(whereClause, Imps.AccountStatus.ACCOUNT, "=", + url.getPathSegments().get(1)); + break; + + case MATCH_BRANDING_RESOURCE_MAP_CACHE: + qb.setTables(TABLE_BRANDING_RESOURCE_MAP_CACHE); + break; + + // mcs and rmq queries + case MATCH_OUTGOING_RMQ_MESSAGES: + qb.setTables(TABLE_OUTGOING_RMQ_MESSAGES); + break; + + case MATCH_OUTGOING_HIGHEST_RMQ_ID: + qb.setTables(TABLE_OUTGOING_RMQ_MESSAGES); + sort = "rmq_id DESC"; + limit = "1"; + break; + + case MATCH_LAST_RMQ_ID: + qb.setTables(TABLE_LAST_RMQ_ID); + limit = "1"; + break; + + case MATCH_S2D_RMQ_IDS: + qb.setTables(TABLE_S2D_RMQ_IDS); + break; + + default: + throw new IllegalArgumentException("Unknown URL " + url); + } + + // run the query + final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + Cursor c = null; + + try { + c = qb.query(db, projectionIn, whereClause.toString(), selectionArgs, + groupBy, null, sort, limit); + if (c != null) { + switch(match) { + case MATCH_CHATTING_CONTACTS: + case MATCH_CONTACTS_BY_PROVIDER: + case MATCH_CHATTING_CONTACTS_BY_PROVIDER: + case MATCH_ONLINE_CONTACTS_BY_PROVIDER: + case MATCH_OFFLINE_CONTACTS_BY_PROVIDER: + case MATCH_CONTACTS_BAREBONE: + case MATCH_CONTACTS_JOIN_PRESENCE: + case MATCH_ONLINE_CONTACT_COUNT: + url = Imps.Contacts.CONTENT_URI; + break; + } + if (DBG) log("set notify url " + url); + c.setNotificationUri(getContext().getContentResolver(), url); + } + } catch (Exception ex) { + Log.e(LOG_TAG, "query db caught ", ex); + } + + return c; + } + + private void buildQueryContactsByProvider(SQLiteQueryBuilder qb, + StringBuilder whereClause, Uri url) { + qb.setTables(CONTACT_JOIN_PRESENCE_CHAT_AVATAR_TABLE); + qb.setProjectionMap(sContactsProjectionMap); + // we don't really need the provider id in query. account id is enough. + appendWhere(whereClause, Imps.Contacts.ACCOUNT, "=", url.getLastPathSegment()); + } + + @Override + public String getType(Uri url) { + int match = mUrlMatcher.match(url); + switch (match) { + case MATCH_PROVIDERS: + return Imps.Provider.CONTENT_TYPE; + + case MATCH_PROVIDERS_BY_ID: + return Imps.Provider.CONTENT_ITEM_TYPE; + + case MATCH_ACCOUNTS: + return Imps.Account.CONTENT_TYPE; + + case MATCH_ACCOUNTS_BY_ID: + return Imps.Account.CONTENT_ITEM_TYPE; + + case MATCH_CONTACTS: + case MATCH_CONTACTS_BY_PROVIDER: + case MATCH_ONLINE_CONTACTS_BY_PROVIDER: + case MATCH_OFFLINE_CONTACTS_BY_PROVIDER: + case MATCH_CONTACTS_BULK: + case MATCH_CONTACTS_BAREBONE: + case MATCH_CONTACTS_JOIN_PRESENCE: + return Imps.Contacts.CONTENT_TYPE; + + case MATCH_CONTACT: + return Imps.Contacts.CONTENT_ITEM_TYPE; + + case MATCH_CONTACTLISTS: + case MATCH_CONTACTLISTS_BY_PROVIDER: + return Imps.ContactList.CONTENT_TYPE; + + case MATCH_CONTACTLIST: + return Imps.ContactList.CONTENT_ITEM_TYPE; + + case MATCH_BLOCKEDLIST: + case MATCH_BLOCKEDLIST_BY_PROVIDER: + return Imps.BlockedList.CONTENT_TYPE; + + case MATCH_CONTACTS_ETAGS: + case MATCH_CONTACTS_ETAG: + return Imps.ContactsEtag.CONTENT_TYPE; + + case MATCH_MESSAGES: + case MATCH_MESSAGES_BY_CONTACT: + case MATCH_MESSAGES_BY_THREAD_ID: + case MATCH_MESSAGES_BY_PROVIDER: + case MATCH_MESSAGES_BY_ACCOUNT: + case MATCH_OTR_MESSAGES: + case MATCH_OTR_MESSAGES_BY_CONTACT: + case MATCH_OTR_MESSAGES_BY_THREAD_ID: + case MATCH_OTR_MESSAGES_BY_PROVIDER: + case MATCH_OTR_MESSAGES_BY_ACCOUNT: + return Imps.Messages.CONTENT_TYPE; + + case MATCH_MESSAGE: + case MATCH_OTR_MESSAGE: + return Imps.Messages.CONTENT_ITEM_TYPE; + + case MATCH_PRESENCE: + case MATCH_PRESENCE_BULK: + return Imps.Presence.CONTENT_TYPE; + + case MATCH_AVATARS: + return Imps.Avatars.CONTENT_TYPE; + + case MATCH_AVATAR: + return Imps.Avatars.CONTENT_ITEM_TYPE; + + case MATCH_CHATS: + return Imps.Chats.CONTENT_TYPE; + + case MATCH_CHATS_ID: + return Imps.Chats.CONTENT_ITEM_TYPE; + + case MATCH_INVITATIONS: + return Imps.Invitation.CONTENT_TYPE; + + case MATCH_INVITATION: + return Imps.Invitation.CONTENT_ITEM_TYPE; + + case MATCH_GROUP_MEMBERS: + case MATCH_GROUP_MEMBERS_BY_GROUP: + return Imps.GroupMembers.CONTENT_TYPE; + + case MATCH_SESSIONS: + case MATCH_SESSIONS_BY_PROVIDER: + return Imps.SessionCookies.CONTENT_TYPE; + + case MATCH_PROVIDER_SETTINGS: + return Imps.ProviderSettings.CONTENT_TYPE; + + case MATCH_ACCOUNTS_STATUS: + return Imps.AccountStatus.CONTENT_TYPE; + + case MATCH_ACCOUNT_STATUS: + return Imps.AccountStatus.CONTENT_ITEM_TYPE; + + default: + throw new IllegalArgumentException("Unknown URL"); + } + } + + // package scope for testing. + boolean insertBulkContacts(ContentValues values) { + //if (DBG) log("insertBulkContacts: begin"); + + ArrayList usernames = values.getStringArrayList(Imps.Contacts.USERNAME); + ArrayList nicknames = values.getStringArrayList(Imps.Contacts.NICKNAME); + int usernameCount = usernames.size(); + int nicknameCount = nicknames.size(); + + if (usernameCount != nicknameCount) { + Log.e(LOG_TAG, "[ImProvider] insertBulkContacts: input bundle " + + "username & nickname lists have diff. length!"); + return false; + } + + ArrayList contactTypeArray = values.getStringArrayList(Imps.Contacts.TYPE); + ArrayList subscriptionStatusArray = + values.getStringArrayList(Imps.Contacts.SUBSCRIPTION_STATUS); + ArrayList subscriptionTypeArray = + values.getStringArrayList(Imps.Contacts.SUBSCRIPTION_TYPE); + ArrayList quickContactArray = values.getStringArrayList(Imps.Contacts.QUICK_CONTACT); + ArrayList rejectedArray = values.getStringArrayList(Imps.Contacts.REJECTED); + int sum = 0; + + final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + + db.beginTransaction(); + try { + Long provider = values.getAsLong(Imps.Contacts.PROVIDER); + Long account = values.getAsLong(Imps.Contacts.ACCOUNT); + Long listId = values.getAsLong(Imps.Contacts.CONTACTLIST); + + ContentValues contactValues = new ContentValues(); + contactValues.put(Imps.Contacts.PROVIDER, provider); + contactValues.put(Imps.Contacts.ACCOUNT, account); + contactValues.put(Imps.Contacts.CONTACTLIST, listId); + ContentValues presenceValues = new ContentValues(); + presenceValues.put(Imps.Presence.PRESENCE_STATUS, + Imps.Presence.OFFLINE); + + for (int i=0; i 0) { + sum++; + + // seed the presence for the new contact + if (DBG) log("### seedPresence for contact id " + rowId); + presenceValues.put(Imps.Presence.CONTACT_ID, rowId); + + try { + db.insert(TABLE_PRESENCE, null, presenceValues); + } catch (android.database.sqlite.SQLiteConstraintException ex) { + Log.w(LOG_TAG, "insertBulkContacts: seeding presence caught " + ex); + } + } + + // yield the lock if anyone else is trying to + // perform a db operation here. + db.yieldIfContended(); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + // We know that we succeeded becuase endTransaction throws if the transaction failed. + if (DBG) log("insertBulkContacts: added " + sum + " contacts!"); + return true; + } + + // package scope for testing. + int updateBulkContacts(ContentValues values, String userWhere) { + ArrayList usernames = values.getStringArrayList(Imps.Contacts.USERNAME); + ArrayList nicknames = values.getStringArrayList(Imps.Contacts.NICKNAME); + + int usernameCount = usernames.size(); + int nicknameCount = nicknames.size(); + + if (usernameCount != nicknameCount) { + Log.e(LOG_TAG, "[ImProvider] updateBulkContacts: input bundle " + + "username & nickname lists have diff. length!"); + return 0; + } + + ArrayList contactTypeArray = values.getStringArrayList(Imps.Contacts.TYPE); + ArrayList subscriptionStatusArray = + values.getStringArrayList(Imps.Contacts.SUBSCRIPTION_STATUS); + ArrayList subscriptionTypeArray = + values.getStringArrayList(Imps.Contacts.SUBSCRIPTION_TYPE); + ArrayList quickContactArray = values.getStringArrayList(Imps.Contacts.QUICK_CONTACT); + ArrayList rejectedArray = values.getStringArrayList(Imps.Contacts.REJECTED); + final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + + db.beginTransaction(); + int sum = 0; + + try { + Long provider = values.getAsLong(Imps.Contacts.PROVIDER); + Long account = values.getAsLong(Imps.Contacts.ACCOUNT); + + ContentValues contactValues = new ContentValues(); + contactValues.put(Imps.Contacts.PROVIDER, provider); + contactValues.put(Imps.Contacts.ACCOUNT, account); + + StringBuilder updateSelection = new StringBuilder(); + String[] updateSelectionArgs = new String[1]; + + for (int i=0; i " + + CONTACTS_WITH_NO_PRESENCE_SELECTION); + } + + c = qb.query(db, + CONTACT_ID_PROJECTION, + CONTACTS_WITH_NO_PRESENCE_SELECTION, + mQueryContactIdSelectionArgs1, + null, null, null, null); + + if (DBG) log("seedInitialPresence: found " + c.getCount() + " contacts w/o presence"); + + count = 0; + + while (c.moveToNext()) { + long id = c.getLong(CONTACT_ID_COLUMN); + presenceValues.put(Imps.Presence.CONTACT_ID, id); + + try { + if (db.insert(TABLE_PRESENCE, null, presenceValues) > 0) { + count++; + } + } catch (SQLiteConstraintException ex) { + // we could possibly catch this exception, since there could be a presence + // row with the same contact_id. That's fine, just ignore the error + if (DBG) log("seedInitialPresence: insert presence for contact_id " + id + + " failed, caught " + ex); + } + } + + if (DBG) log("seedInitialPresence: added " + count + " new presence rows"); + + db.setTransactionSuccessful(); + } finally { + if (c != null) { + c.close(); + } + db.endTransaction(); + } + } + + private int updateBulkPresence(ContentValues values, String userWhere, String[] whereArgs) { + ArrayList usernames = values.getStringArrayList(Imps.Contacts.USERNAME); + int count = usernames.size(); + Long account = values.getAsLong(Imps.Contacts.ACCOUNT); + + ArrayList priorityArray = values.getStringArrayList(Imps.Presence.PRIORITY); + ArrayList modeArray = values.getStringArrayList(Imps.Presence.PRESENCE_STATUS); + ArrayList statusArray = values.getStringArrayList( + Imps.Presence.PRESENCE_CUSTOM_STATUS); + ArrayList clientTypeArray = values.getStringArrayList(Imps.Presence.CLIENT_TYPE); + ArrayList resourceArray = values.getStringArrayList(Imps.Presence.JID_RESOURCE); + + // append username to the selection clause + StringBuilder buf = new StringBuilder(); + + if (!TextUtils.isEmpty(userWhere)) { + buf.append(userWhere); + buf.append(" AND "); + } + + buf.append(Imps.Presence.CONTACT_ID); + buf.append(" in (select "); + buf.append(Imps.Contacts._ID); + buf.append(" from "); + buf.append(TABLE_CONTACTS); + buf.append(" where "); + buf.append(Imps.Contacts.ACCOUNT); + buf.append("=? AND "); + + // use username LIKE ? for case insensitive comparison + buf.append(Imps.Contacts.USERNAME); + buf.append(" LIKE ?) AND ("); + + buf.append(Imps.Presence.PRIORITY); + buf.append("<=? OR "); + buf.append(Imps.Presence.PRIORITY); + buf.append(" IS NULL OR "); + buf.append(Imps.Presence.JID_RESOURCE); + buf.append("=?)"); + + String selection = buf.toString(); + + if (DBG) log("updateBulkPresence: selection => " + selection); + + int numArgs = (whereArgs != null ? whereArgs.length + 4 : 4); + String[] selectionArgs = new String[numArgs]; + int selArgsIndex = 0; + + if (whereArgs != null) { + for (selArgsIndex=0; selArgsIndex 0) { + resultUri = Uri.parse(Imps.Provider.CONTENT_URI + "/" + rowID); + } + notifyProviderAccountContentUri = true; + break; + + case MATCH_ACCOUNTS: + // Insert into the accounts table + rowID = db.insert(TABLE_ACCOUNTS, "name", initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.Account.CONTENT_URI + "/" + rowID); + } + notifyProviderAccountContentUri = true; + break; + + case MATCH_CONTACTS_BY_PROVIDER: + appendValuesFromUrl(initialValues, url, Imps.Contacts.PROVIDER, + Imps.Contacts.ACCOUNT); + // fall through + case MATCH_CONTACTS: + case MATCH_CONTACTS_BAREBONE: + // Insert into the contacts table + rowID = db.insert(TABLE_CONTACTS, "username", initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.Contacts.CONTENT_URI + "/" + rowID); + } + + notifyContactContentUri = true; + break; + + case MATCH_CONTACTS_BULK: + if (insertBulkContacts(initialValues)) { + // notify change using the "content://im/contacts" url, + // so the change will be observed by listeners interested + // in contacts changes. + resultUri = Imps.Contacts.CONTENT_URI; + } + notifyContactContentUri = true; + break; + + case MATCH_CONTACTLISTS_BY_PROVIDER: + appendValuesFromUrl(initialValues, url, Imps.ContactList.PROVIDER, + Imps.ContactList.ACCOUNT); + // fall through + case MATCH_CONTACTLISTS: + // Insert into the contactList table + rowID = db.insert(TABLE_CONTACT_LIST, "name", initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.ContactList.CONTENT_URI + "/" + rowID); + } + notifyContactListContentUri = true; + break; + + case MATCH_BLOCKEDLIST_BY_PROVIDER: + appendValuesFromUrl(initialValues, url, Imps.BlockedList.PROVIDER, + Imps.BlockedList.ACCOUNT); + // fall through + case MATCH_BLOCKEDLIST: + // Insert into the blockedList table + rowID = db.insert(TABLE_BLOCKED_LIST, "username", initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.BlockedList.CONTENT_URI + "/" + rowID); + } + + break; + + case MATCH_CONTACTS_ETAGS: + rowID = db.replace(TABLE_CONTACTS_ETAG, Imps.ContactsEtag.ETAG, initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.ContactsEtag.CONTENT_URI + "/" + rowID); + } + break; + + case MATCH_MESSAGES_BY_CONTACT: + String accountStr = decodeURLSegment(url.getPathSegments().get(1)); + try { + account = Long.parseLong(accountStr); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + + contact = decodeURLSegment(url.getPathSegments().get(2)); + initialValues.put(Imps.Messages.THREAD_ID, getContactId(db, accountStr, contact)); + + notifyMessagesContentUri = true; + + // Insert into the messages table. + rowID = db.insert(TABLE_MESSAGES, "thread_id", initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.Messages.CONTENT_URI + "/" + rowID); + } + + break; + + case MATCH_MESSAGES_BY_THREAD_ID: + appendValuesFromUrl(initialValues, url, Imps.Messages.THREAD_ID); + // fall through + + case MATCH_MESSAGES: + // Insert into the messages table. + notifyMessagesContentUri = true; + rowID = db.insert(TABLE_MESSAGES, "thread_id", initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.Messages.CONTENT_URI + "/" + rowID); + } + + break; + + case MATCH_OTR_MESSAGES_BY_CONTACT: + String accountStr2 = decodeURLSegment(url.getPathSegments().get(1)); + + try { + account = Long.parseLong(accountStr2); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + + contact = decodeURLSegment(url.getPathSegments().get(2)); + initialValues.put(Imps.Messages.THREAD_ID, getContactId(db, accountStr2, contact)); + + notifyMessagesByContactContentUri = true; + + // Insert into the in-memory messages table. + rowID = db.insert(TABLE_IN_MEMORY_MESSAGES, "thread_id", initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.Messages.OTR_MESSAGES_CONTENT_URI + "/" + rowID); + } + + break; + + case MATCH_OTR_MESSAGES_BY_THREAD_ID: + try { + threadId = Long.parseLong(decodeURLSegment(url.getPathSegments().get(1))); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + + initialValues.put(Imps.Messages.THREAD_ID, threadId); + + notifyMessagesByThreadIdContentUri = true; + // fall through + + case MATCH_OTR_MESSAGES: + // Insert into the messages table. + rowID = db.insert(TABLE_IN_MEMORY_MESSAGES, "thread_id", initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.Messages.OTR_MESSAGES_CONTENT_URI + "/" + rowID); + } + + break; + + case MATCH_INVITATIONS: + rowID = db.insert(TABLE_INVITATIONS, null, initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.Invitation.CONTENT_URI + "/" + rowID); + } + break; + + case MATCH_GROUP_MEMBERS: + rowID = db.insert(TABLE_GROUP_MEMBERS, "nickname", initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.GroupMembers.CONTENT_URI + "/" + rowID); + } + break; + + case MATCH_GROUP_MEMBERS_BY_GROUP: + appendValuesFromUrl(initialValues, url, Imps.GroupMembers.GROUP); + rowID = db.insert(TABLE_GROUP_MEMBERS, "nickname", initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.GroupMembers.CONTENT_URI + "/" + rowID); + } + break; + + case MATCH_AVATAR_BY_PROVIDER: + appendValuesFromUrl(initialValues, url, Imps.Avatars.PROVIDER, Imps.Avatars.ACCOUNT); + // fall through + case MATCH_AVATARS: + // Insert into the avatars table + rowID = db.replace(TABLE_AVATARS, "contact", initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.Avatars.CONTENT_URI + "/" + rowID); + } + break; + + case MATCH_CHATS_ID: + appendValuesFromUrl(initialValues, url, Imps.Chats.CONTACT_ID); + // fall through + case MATCH_CHATS: + // Insert into the chats table + initialValues.put(Imps.Chats.SHORTCUT, -1); + rowID = db.replace(TABLE_CHATS, Imps.Chats.CONTACT_ID, initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.Chats.CONTENT_URI + "/" + rowID); + addToQuickSwitch(rowID); + } + notifyContactContentUri = true; + break; + + case MATCH_PRESENCE: + rowID = db.replace(TABLE_PRESENCE, null, initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.Presence.CONTENT_URI + "/" + rowID); + } + notifyContactContentUri = true; + break; + + case MATCH_PRESENCE_SEED_BY_ACCOUNT: + try { + seedInitialPresenceByAccount(Long.parseLong(url.getLastPathSegment())); + resultUri = Imps.Presence.CONTENT_URI; + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + break; + + case MATCH_SESSIONS_BY_PROVIDER: + appendValuesFromUrl(initialValues, url, Imps.SessionCookies.PROVIDER, + Imps.SessionCookies.ACCOUNT); + // fall through + case MATCH_SESSIONS: + rowID = db.insert(TABLE_SESSION_COOKIES, null, initialValues); + if(rowID > 0) { + resultUri = Uri.parse(Imps.SessionCookies.CONTENT_URI + "/" + rowID); + } + break; + + case MATCH_PROVIDER_SETTINGS: + rowID = db.replace(TABLE_PROVIDER_SETTINGS, null, initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.ProviderSettings.CONTENT_URI + "/" + rowID); + } + break; + + case MATCH_ACCOUNTS_STATUS: + rowID = db.replace(TABLE_ACCOUNT_STATUS, null, initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.AccountStatus.CONTENT_URI + "/" + rowID); + } + notifyProviderAccountContentUri = true; + break; + + case MATCH_BRANDING_RESOURCE_MAP_CACHE: + rowID = db.insert(TABLE_BRANDING_RESOURCE_MAP_CACHE, null, initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.BrandingResourceMapCache.CONTENT_URI + "/" + rowID); + } + break; + + // mcs/rmq stuff + case MATCH_OUTGOING_RMQ_MESSAGES: + rowID = db.insert(TABLE_OUTGOING_RMQ_MESSAGES, null, initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.OutgoingRmq.CONTENT_URI + "/" + rowID); + } + break; + + case MATCH_LAST_RMQ_ID: + rowID = db.replace(TABLE_LAST_RMQ_ID, null, initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.LastRmqId.CONTENT_URI + "/" + rowID); + } + break; + + case MATCH_S2D_RMQ_IDS: + rowID = db.insert(TABLE_S2D_RMQ_IDS, null, initialValues); + if (rowID > 0) { + resultUri = Uri.parse(Imps.ServerToDeviceRmqIds.CONTENT_URI + "/" + rowID); + } + break; + + default: + throw new UnsupportedOperationException("Cannot insert into URL: " + url); + } + // TODO: notify the data change observer? + + if (resultUri != null) { + ContentResolver resolver = getContext().getContentResolver(); + + // In most case, we query contacts with presence and chats joined, thus + // we should also notify that contacts changes when presence or chats changed. + if (notifyContactContentUri) { + resolver.notifyChange(Imps.Contacts.CONTENT_URI, null); + } + + if (notifyContactListContentUri) { + resolver.notifyChange(Imps.ContactList.CONTENT_URI, null); + } + + if (notifyMessagesContentUri) { + resolver.notifyChange(Imps.Messages.CONTENT_URI, null); + } + + if (notifyMessagesByContactContentUri) { + resolver.notifyChange(Imps.Messages.CONTENT_URI, null); + resolver.notifyChange(Imps.Messages.getContentUriByContact(account, contact), null); + } + + if (notifyMessagesByThreadIdContentUri) { + resolver.notifyChange(Imps.Messages.CONTENT_URI, null); + resolver.notifyChange(Imps.Messages.getContentUriByThreadId(threadId), null); + } + + if (notifyProviderAccountContentUri) { + if (DBG) log("notify insert for " + Imps.Provider.CONTENT_URI_WITH_ACCOUNT); + resolver.notifyChange(Imps.Provider.CONTENT_URI_WITH_ACCOUNT, null); + } + } + return resultUri; + } + + private void appendValuesFromUrl(ContentValues values, Uri url, String...columns){ + if(url.getPathSegments().size() <= columns.length) { + throw new IllegalArgumentException("Not enough values in url"); + } + for(int i = 0; i < columns.length; i++){ + if(values.containsKey(columns[i])){ + throw new UnsupportedOperationException("Cannot override the value for " + columns[i]); + } + values.put(columns[i], decodeURLSegment(url.getPathSegments().get(i + 1))); + } + } + + private long getContactId(final SQLiteDatabase db, + final String accountId, final String contact) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(TABLE_CONTACTS); + qb.setProjectionMap(sContactsProjectionMap); + + mQueryContactIdSelectionArgs2[0] = accountId; + mQueryContactIdSelectionArgs2[1] = contact; + + Cursor c = qb.query(db, + CONTACT_ID_PROJECTION, + CONTACT_ID_QUERY_SELECTION, + mQueryContactIdSelectionArgs2, + null, null, null, null); + + long contactId = 0; + + try { + if (c.moveToFirst()) { + contactId = c.getLong(CONTACT_ID_COLUMN); + } + } finally { + c.close(); + } + + return contactId; + } + + // Quick-switch management + // The chat UI provides slots (0, 9, .., 1) for the first 10 chats. This allows you to + // quickly switch between these chats by chording menu+#. We number from the right end of + // the number row and move leftward to make an easier two-hand chord with the menu button + // on the left side of the keyboard. + private void addToQuickSwitch(long newRow) { + // Since there are fewer than 10, there must be an empty slot. Let's find it. + int slot = findEmptyQuickSwitchSlot(); + + if (slot == -1) { + return; + } + + updateSlotForChat(newRow, slot); + } + + // If there are more than 10 chats and one with a quick switch slot ends then pick a chat + // that doesn't have a slot and have it inhabit the newly emptied slot. + private void backfillQuickSwitchSlots() { + // Find all the chats without a quick switch slot, and order + Cursor c = query(Imps.Chats.CONTENT_URI, + BACKFILL_PROJECTION, + Imps.Chats.SHORTCUT + "=-1", null, Imps.Chats.LAST_MESSAGE_DATE + " DESC"); + + try { + if (c.getCount() < 1) { + return; + } + + int slot = findEmptyQuickSwitchSlot(); + + if (slot != -1) { + c.moveToFirst(); + + long id = c.getLong(c.getColumnIndex(Imps.Chats._ID)); + + updateSlotForChat(id, slot); + } + } finally { + c.close(); + } + } + + private int updateSlotForChat(long chatId, int slot) { + ContentValues values = new ContentValues(); + + values.put(Imps.Chats.SHORTCUT, slot); + + return update(Imps.Chats.CONTENT_URI, values, Imps.Chats._ID + "=?", + new String[] { Long.toString(chatId) }); + } + + private int findEmptyQuickSwitchSlot() { + Cursor c = queryInternal(Imps.Chats.CONTENT_URI, FIND_SHORTCUT_PROJECTION, null, null, null); + final int N = c.getCount(); + + try { + // If there are 10 or more chats then all the quick switch slots are already filled + if (N >= 10) { + return -1; + } + + int slots = 0; + int column = c.getColumnIndex(Imps.Chats.SHORTCUT); + + // The map is here because numbers go from 0-9, but we want to assign slots in + // 0, 9, 8, ..., 1 order to match the right-to-left reading of the number row + // on the keyboard. + int[] map = new int[] { 0, 9, 8, 7, 6, 5, 4, 3, 2, 1 }; + + // Mark all the slots that are in use + // The shortcuts represent actual keyboard number row keys, and not ordinals. + // So 7 would mean the shortcut is the 7 key on the keyboard and NOT the 7th + // shortcut. The passing of slot through map[] below maps these keyboard key + // shortcuts into an ordinal bit position in the 'slots' bitfield. + for (c.moveToFirst(); ! c.isAfterLast(); c.moveToNext()) { + int slot = c.getInt(column); + + if (slot != -1) { + slots |= (1 << map[slot]); + } + } + + // Try to find an empty one + // As we exit this, the push of i through map[] maps the ordinal bit position + // in the 'slots' bitfield onto a key on the number row of the device keyboard. + // The keyboard key is what is used to designate the shortcut. + for (int i = 0; i < 10; i++) { + if ((slots & (1 << i)) == 0) { + return map[i]; + } + } + + return -1; + } finally { + c.close(); + } + } + + /** + * manual trigger for deleting contacts + */ + private static final String DELETE_PRESENCE_SELECTION = + Imps.Presence.CONTACT_ID + " in (select " + + PRESENCE_CONTACT_ID + " from " + TABLE_PRESENCE + " left outer join " + TABLE_CONTACTS + + " on " + PRESENCE_CONTACT_ID + '=' + CONTACT_ID + " where " + CONTACT_ID + " IS NULL)"; + + private static final String CHATS_CONTACT_ID = TABLE_CHATS + '.' + Imps.Chats.CONTACT_ID; + private static final String DELETE_CHATS_SELECTION = Imps.Chats.CONTACT_ID + " in (select "+ + CHATS_CONTACT_ID + " from " + TABLE_CHATS + " left outer join " + TABLE_CONTACTS + + " on " + CHATS_CONTACT_ID + '=' + CONTACT_ID + " where " + CONTACT_ID + " IS NULL)"; + + private static final String GROUP_MEMBER_ID = TABLE_GROUP_MEMBERS + '.' + Imps.GroupMembers.GROUP; + private static final String DELETE_GROUP_MEMBER_SELECTION = + Imps.GroupMembers.GROUP + " in (select "+ + GROUP_MEMBER_ID + " from " + TABLE_GROUP_MEMBERS + " left outer join " + TABLE_CONTACTS + + " on " + GROUP_MEMBER_ID + '=' + CONTACT_ID + " where " + CONTACT_ID + " IS NULL)"; + + private static final String GROUP_MESSAGES_ID = TABLE_MESSAGES + '.' + Imps.Messages.THREAD_ID; + private static final String DELETE_GROUP_MESSAGES_SELECTION = + Imps.Messages.THREAD_ID + " in (select "+ GROUP_MESSAGES_ID + " from " + + TABLE_MESSAGES + " left outer join " + TABLE_CONTACTS + " on " + + GROUP_MESSAGES_ID + '=' + CONTACT_ID + " where " + CONTACT_ID + " IS NULL)"; + + private void performContactRemovalCleanup(long contactId) { + final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + + if (contactId > 0) { + StringBuilder buf = new StringBuilder(); + + // delete presence + buf.append(Imps.Presence.CONTACT_ID).append('=').append(contactId); + deleteWithSelection(db, TABLE_PRESENCE, buf.toString(), null); + + // delete group memebers + buf.delete(0, buf.length()); + buf.append(Imps.GroupMembers.GROUP).append('=').append(contactId); + deleteWithSelection(db, TABLE_GROUP_MEMBERS, buf.toString(), null); + } else { + // delete presence + deleteWithSelection(db, TABLE_PRESENCE, DELETE_PRESENCE_SELECTION, null); + + // delete group members + deleteWithSelection(db, TABLE_GROUP_MEMBERS, DELETE_GROUP_MEMBER_SELECTION, null); + } + } + + private void deleteWithSelection(SQLiteDatabase db, String tableName, + String selection, String[] selectionArgs) { + if (DBG) log("deleteWithSelection: table " + tableName + ", selection => " + selection); + int count = db.delete(tableName, selection, selectionArgs); + if (DBG) log("deleteWithSelection: deleted " + count + " rows"); + } + + private String buildContactIdSelection(String columnName, String contactSelection) { + StringBuilder buf = new StringBuilder(); + + buf.append(columnName); + buf.append(" in (select "); + buf.append(Imps.Contacts._ID); + buf.append(" from "); + buf.append(TABLE_CONTACTS); + buf.append(" where "); + buf.append(contactSelection); + buf.append(")"); + + return buf.toString(); + } + + private int deleteInternal(Uri url, String userWhere, String[] whereArgs) { + String tableToChange; + + // In some cases a given url requires that we delete rows from more than one + // table. The motivating example is deleting messages from both the on disk + // and in memory messages tables. + String tableToChange2 = null; + String idColumnName = null; + String changedItemId = null; + String provider = null; + String accountStr = null; + long account = 0; + String contact = null; + long threadId = 0; + + StringBuilder whereClause = new StringBuilder(); + if(userWhere != null) { + whereClause.append(userWhere); + } + + boolean notifyMessagesContentUri = false; + boolean notifyMessagesByContactContentUri = false; + boolean notifyMessagesByThreadIdContentUri = false; + boolean notifyContactListContentUri = false; + boolean notifyProviderAccountContentUri = false; + int match = mUrlMatcher.match(url); + + boolean contactDeleted = false; + long deletedContactId = 0; + + boolean backfillQuickSwitchSlots = false; + + final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + + switch (match) { + case MATCH_PROVIDERS: + tableToChange = TABLE_PROVIDERS; + notifyProviderAccountContentUri = true; + break; + + case MATCH_ACCOUNTS_BY_ID: + changedItemId = url.getPathSegments().get(1); + // fall through + case MATCH_ACCOUNTS: + tableToChange = TABLE_ACCOUNTS; + notifyProviderAccountContentUri = true; + break; + + case MATCH_ACCOUNT_STATUS: + changedItemId = url.getPathSegments().get(1); + // fall through + case MATCH_ACCOUNTS_STATUS: + tableToChange = TABLE_ACCOUNT_STATUS; + notifyProviderAccountContentUri = true; + break; + + case MATCH_CONTACTS: + case MATCH_CONTACTS_BAREBONE: + tableToChange = TABLE_CONTACTS; + contactDeleted = true; + break; + + case MATCH_CONTACT: + tableToChange = TABLE_CONTACTS; + changedItemId = url.getPathSegments().get(1); + + try { + deletedContactId = Long.parseLong(changedItemId); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + + contactDeleted = true; + break; + + case MATCH_CONTACTS_BY_PROVIDER: + tableToChange = TABLE_CONTACTS; + appendWhere(whereClause, Imps.Contacts.ACCOUNT, "=", url.getPathSegments().get(2)); + contactDeleted = true; + break; + + case MATCH_CONTACTLISTS_BY_PROVIDER: + appendWhere(whereClause, Imps.ContactList.ACCOUNT, "=", + url.getPathSegments().get(2)); + // fall through + case MATCH_CONTACTLISTS: + tableToChange = TABLE_CONTACT_LIST; + notifyContactListContentUri = true; + break; + + case MATCH_CONTACTLIST: + tableToChange = TABLE_CONTACT_LIST; + changedItemId = url.getPathSegments().get(1); + break; + + case MATCH_BLOCKEDLIST: + tableToChange = TABLE_BLOCKED_LIST; + break; + + case MATCH_BLOCKEDLIST_BY_PROVIDER: + tableToChange = TABLE_BLOCKED_LIST; + appendWhere(whereClause, Imps.BlockedList.ACCOUNT, "=", url.getPathSegments().get(2)); + break; + + case MATCH_CONTACTS_ETAGS: + tableToChange = TABLE_CONTACTS_ETAG; + break; + + case MATCH_CONTACTS_ETAG: + tableToChange = TABLE_CONTACTS_ETAG; + changedItemId = url.getPathSegments().get(1); + break; + + case MATCH_MESSAGES: + tableToChange = TABLE_MESSAGES; + break; + + case MATCH_MESSAGES_BY_CONTACT: + tableToChange = TABLE_MESSAGES; + tableToChange2 = TABLE_IN_MEMORY_MESSAGES; + + accountStr = decodeURLSegment(url.getPathSegments().get(1)); + try { + account = Long.parseLong(accountStr); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + + contact = decodeURLSegment(url.getPathSegments().get(2)); + appendWhere(whereClause, Imps.Messages.THREAD_ID, "=", + getContactId(db, accountStr, contact)); + + notifyMessagesContentUri = true; + break; + + case MATCH_MESSAGES_BY_THREAD_ID: + tableToChange = TABLE_MESSAGES; + tableToChange2 = TABLE_IN_MEMORY_MESSAGES; + + try { + threadId = Long.parseLong(decodeURLSegment(url.getPathSegments().get(1))); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + + appendWhere(whereClause, Imps.Messages.THREAD_ID, "=", threadId); + + notifyMessagesContentUri = true; + break; + + case MATCH_MESSAGES_BY_PROVIDER: + tableToChange = TABLE_MESSAGES; + + provider = decodeURLSegment(url.getPathSegments().get(1)); + appendWhere(whereClause, buildContactIdSelection(Imps.Messages.THREAD_ID, + Imps.Contacts.PROVIDER + "='" + provider + "'")); + + notifyMessagesContentUri = true; + break; + + case MATCH_MESSAGES_BY_ACCOUNT: + tableToChange = TABLE_MESSAGES; + + accountStr = decodeURLSegment(url.getPathSegments().get(1)); + appendWhere(whereClause, buildContactIdSelection(Imps.Messages.THREAD_ID, + Imps.Contacts.ACCOUNT + "='" + accountStr + "'")); + + notifyMessagesContentUri = true; + break; + + case MATCH_MESSAGE: + tableToChange = TABLE_MESSAGES; + changedItemId = url.getPathSegments().get(1); + notifyMessagesContentUri = true; + break; + + case MATCH_OTR_MESSAGES: + tableToChange = TABLE_IN_MEMORY_MESSAGES; + break; + + case MATCH_OTR_MESSAGES_BY_CONTACT: + tableToChange = TABLE_IN_MEMORY_MESSAGES; + + accountStr = decodeURLSegment(url.getPathSegments().get(1)); + try { + account = Long.parseLong(accountStr); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + + contact = decodeURLSegment(url.getPathSegments().get(2)); + appendWhere(whereClause, Imps.Messages.THREAD_ID, "=", + getContactId(db, accountStr, contact)); + + notifyMessagesByContactContentUri = true; + break; + + case MATCH_OTR_MESSAGES_BY_THREAD_ID: + tableToChange = TABLE_IN_MEMORY_MESSAGES; + + try { + threadId = Long.parseLong(decodeURLSegment(url.getPathSegments().get(1))); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + + appendWhere(whereClause, Imps.Messages.THREAD_ID, "=", threadId); + + notifyMessagesByThreadIdContentUri = true; + break; + + case MATCH_OTR_MESSAGES_BY_PROVIDER: + tableToChange = TABLE_IN_MEMORY_MESSAGES; + + provider = decodeURLSegment(url.getPathSegments().get(1)); + appendWhere(whereClause, buildContactIdSelection(Imps.Messages.THREAD_ID, + Imps.Contacts.PROVIDER + "='" + provider + "'")); + + if (DBG) log("delete (MATCH_OTR_MESSAGES_BY_PROVIDER) sel => " + whereClause); + notifyMessagesContentUri = true; + break; + + case MATCH_OTR_MESSAGES_BY_ACCOUNT: + tableToChange = TABLE_IN_MEMORY_MESSAGES; + + accountStr = decodeURLSegment(url.getPathSegments().get(1)); + appendWhere(whereClause, buildContactIdSelection(Imps.Messages.THREAD_ID, + Imps.Contacts.ACCOUNT + "='" + accountStr + "'")); + + if (DBG) log("delete (MATCH_OTR_MESSAGES_BY_ACCOUNT) sel => " + whereClause); + notifyMessagesContentUri = true; + break; + + case MATCH_OTR_MESSAGE: + tableToChange = TABLE_IN_MEMORY_MESSAGES; + changedItemId = url.getPathSegments().get(1); + notifyMessagesContentUri = true; + break; + + case MATCH_GROUP_MEMBERS: + tableToChange = TABLE_GROUP_MEMBERS; + break; + + case MATCH_GROUP_MEMBERS_BY_GROUP: + tableToChange = TABLE_GROUP_MEMBERS; + appendWhere(whereClause, Imps.GroupMembers.GROUP, "=", url.getPathSegments().get(1)); + break; + + case MATCH_INVITATIONS: + tableToChange = TABLE_INVITATIONS; + break; + + case MATCH_INVITATION: + tableToChange = TABLE_INVITATIONS; + changedItemId = url.getPathSegments().get(1); + break; + + case MATCH_AVATARS: + tableToChange = TABLE_AVATARS; + break; + + case MATCH_AVATAR: + tableToChange = TABLE_AVATARS; + changedItemId = url.getPathSegments().get(1); + break; + + case MATCH_AVATAR_BY_PROVIDER: + tableToChange = TABLE_AVATARS; + changedItemId = url.getPathSegments().get(2); + idColumnName = Imps.Avatars.ACCOUNT; + break; + + case MATCH_CHATS: + tableToChange = TABLE_CHATS; + backfillQuickSwitchSlots = true; + break; + + case MATCH_CHATS_BY_ACCOUNT: + tableToChange = TABLE_CHATS; + + accountStr = decodeURLSegment(url.getLastPathSegment()); + appendWhere(whereClause, buildContactIdSelection(Imps.Chats.CONTACT_ID, + Imps.Contacts.ACCOUNT + "='" + accountStr + "'")); + + if (DBG) log("delete (MATCH_CHATS_BY_ACCOUNT) sel => " + whereClause); + + changedItemId = null; + break; + + case MATCH_CHATS_ID: + tableToChange = TABLE_CHATS; + changedItemId = url.getPathSegments().get(1); + idColumnName = Imps.Chats.CONTACT_ID; + break; + + case MATCH_PRESENCE: + tableToChange = TABLE_PRESENCE; + break; + + case MATCH_PRESENCE_ID: + tableToChange = TABLE_PRESENCE; + changedItemId = url.getPathSegments().get(1); + idColumnName = Imps.Presence.CONTACT_ID; + break; + + case MATCH_PRESENCE_BY_ACCOUNT: + tableToChange = TABLE_PRESENCE; + + accountStr = decodeURLSegment(url.getLastPathSegment()); + appendWhere(whereClause, buildContactIdSelection(Imps.Presence.CONTACT_ID, + Imps.Contacts.ACCOUNT + "='" + accountStr + "'")); + + if (DBG) log("delete (MATCH_PRESENCE_BY_ACCOUNT): sel => " + whereClause); + changedItemId = null; + break; + + case MATCH_SESSIONS: + tableToChange = TABLE_SESSION_COOKIES; + break; + + case MATCH_SESSIONS_BY_PROVIDER: + tableToChange = TABLE_SESSION_COOKIES; + changedItemId = url.getPathSegments().get(2); + idColumnName = Imps.SessionCookies.ACCOUNT; + break; + + case MATCH_PROVIDER_SETTINGS_BY_ID: + tableToChange = TABLE_PROVIDER_SETTINGS; + changedItemId = url.getPathSegments().get(1); + idColumnName = Imps.ProviderSettings.PROVIDER; + break; + + case MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME: + tableToChange = TABLE_PROVIDER_SETTINGS; + + String providerId = url.getPathSegments().get(1); + String name = url.getPathSegments().get(2); + + appendWhere(whereClause, Imps.ProviderSettings.PROVIDER, "=", providerId); + appendWhere(whereClause, Imps.ProviderSettings.NAME, "=", name); + break; + + case MATCH_BRANDING_RESOURCE_MAP_CACHE: + tableToChange = TABLE_BRANDING_RESOURCE_MAP_CACHE; + break; + + // mcs/rmq stuff + case MATCH_OUTGOING_RMQ_MESSAGES: + tableToChange = TABLE_OUTGOING_RMQ_MESSAGES; + break; + + case MATCH_LAST_RMQ_ID: + tableToChange = TABLE_LAST_RMQ_ID; + break; + + case MATCH_S2D_RMQ_IDS: + tableToChange = TABLE_S2D_RMQ_IDS; + break; + + default: + throw new UnsupportedOperationException("Cannot delete that URL: " + url); + } + + if (idColumnName == null) { + idColumnName = "_id"; + } + + if (changedItemId != null) { + appendWhere(whereClause, idColumnName, "=", changedItemId); + } + + if (DBG) log("delete from " + url + " WHERE " + whereClause); + + int count = db.delete(tableToChange, whereClause.toString(), whereArgs); + + // see the comment at the declaration of tableToChange2 for an explanation + if (tableToChange2 != null){ + count += db.delete(tableToChange2, whereClause.toString(), whereArgs); + } + + if (contactDeleted && count > 0) { + // since the contact cleanup triggers no longer work for cross database tables, + // we have to do it by hand here. + performContactRemovalCleanup(deletedContactId); + } + + if (count > 0) { + ContentResolver resolver = getContext().getContentResolver(); + + // In most case, we query contacts with presence and chats joined, thus + // we should also notify that contacts changes when presence or chats changed. + if (match == MATCH_CHATS || match == MATCH_CHATS_ID + || match == MATCH_PRESENCE || match == MATCH_PRESENCE_ID + || match == MATCH_CONTACTS_BAREBONE) { + resolver.notifyChange(Imps.Contacts.CONTENT_URI, null); + } + + if (notifyMessagesContentUri) { + resolver.notifyChange(Imps.Messages.CONTENT_URI, null); + } + + if (notifyMessagesByContactContentUri) { + resolver.notifyChange(Imps.Messages.CONTENT_URI, null); + resolver.notifyChange(Imps.Messages.getContentUriByContact(account, contact), null); + } + + if (notifyMessagesByThreadIdContentUri) { + resolver.notifyChange(Imps.Messages.CONTENT_URI, null); + resolver.notifyChange(Imps.Messages.getContentUriByThreadId(threadId), null); + } + + if (notifyContactListContentUri) { + resolver.notifyChange(Imps.ContactList.CONTENT_URI, null); + } + + if (notifyProviderAccountContentUri) { + if (DBG) log("notify delete for " + Imps.Provider.CONTENT_URI_WITH_ACCOUNT); + resolver.notifyChange(Imps.Provider.CONTENT_URI_WITH_ACCOUNT, null); + } + + if (backfillQuickSwitchSlots) { + backfillQuickSwitchSlots(); + } + } + + return count; + } + + private int updateInternal(Uri url, ContentValues values, String userWhere, + String[] whereArgs) { + String tableToChange; + String idColumnName = null; + String changedItemId = null; + String accountStr = null; + long account = 0; + String contact = null; + long threadId = 0; + int count; + + StringBuilder whereClause = new StringBuilder(); + if(userWhere != null) { + whereClause.append(userWhere); + } + + boolean notifyMessagesContentUri = false; + boolean notifyMessagesByContactContentUri = false; + boolean notifyMessagesByThreadIdContentUri = false; + boolean notifyContactListContentUri = false; + boolean notifyProviderAccountContentUri = false; + + int match = mUrlMatcher.match(url); + final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + + switch (match) { + case MATCH_PROVIDERS_BY_ID: + changedItemId = url.getPathSegments().get(1); + // fall through + case MATCH_PROVIDERS: + tableToChange = TABLE_PROVIDERS; + break; + + case MATCH_ACCOUNTS_BY_ID: + changedItemId = url.getPathSegments().get(1); + // fall through + case MATCH_ACCOUNTS: + tableToChange = TABLE_ACCOUNTS; + notifyProviderAccountContentUri = true; + break; + + case MATCH_ACCOUNT_STATUS: + changedItemId = url.getPathSegments().get(1); + // fall through + case MATCH_ACCOUNTS_STATUS: + tableToChange = TABLE_ACCOUNT_STATUS; + notifyProviderAccountContentUri = true; + break; + + case MATCH_CONTACTS: + case MATCH_CONTACTS_BAREBONE: + tableToChange = TABLE_CONTACTS; + break; + + case MATCH_CONTACTS_BY_PROVIDER: + tableToChange = TABLE_CONTACTS; + changedItemId = url.getPathSegments().get(2); + idColumnName = Imps.Contacts.ACCOUNT; + break; + + case MATCH_CONTACT: + tableToChange = TABLE_CONTACTS; + changedItemId = url.getPathSegments().get(1); + break; + + case MATCH_CONTACTS_BULK: + count = updateBulkContacts(values, userWhere); + // notify change using the "content://im/contacts" url, + // so the change will be observed by listeners interested + // in contacts changes. + if (count > 0) { + getContext().getContentResolver().notifyChange( + Imps.Contacts.CONTENT_URI, null); + } + return count; + + case MATCH_CONTACTLIST: + tableToChange = TABLE_CONTACT_LIST; + changedItemId = url.getPathSegments().get(1); + notifyContactListContentUri = true; + break; + + case MATCH_CONTACTS_ETAGS: + tableToChange = TABLE_CONTACTS_ETAG; + break; + + case MATCH_CONTACTS_ETAG: + tableToChange = TABLE_CONTACTS_ETAG; + changedItemId = url.getPathSegments().get(1); + break; + + case MATCH_MESSAGES: + tableToChange = TABLE_MESSAGES; + break; + + case MATCH_MESSAGES_BY_CONTACT: + tableToChange = TABLE_MESSAGES; + + accountStr = decodeURLSegment(url.getPathSegments().get(1)); + try { + account = Long.parseLong(accountStr); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + + contact = decodeURLSegment(url.getPathSegments().get(2)); + appendWhere(whereClause, Imps.Messages.THREAD_ID, "=", + getContactId(db, accountStr, contact)); + + notifyMessagesContentUri = true; + break; + + case MATCH_MESSAGES_BY_THREAD_ID: + tableToChange = TABLE_MESSAGES; + + try { + threadId = Long.parseLong(decodeURLSegment(url.getPathSegments().get(1))); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + + appendWhere(whereClause, Imps.Messages.THREAD_ID, "=", threadId); + + notifyMessagesContentUri = true; + break; + + case MATCH_MESSAGE: + tableToChange = TABLE_MESSAGES; + changedItemId = url.getPathSegments().get(1); + notifyMessagesContentUri = true; + break; + + case MATCH_OTR_MESSAGES: + tableToChange = TABLE_IN_MEMORY_MESSAGES; + break; + + case MATCH_OTR_MESSAGES_BY_CONTACT: + tableToChange = TABLE_IN_MEMORY_MESSAGES; + + accountStr = decodeURLSegment(url.getPathSegments().get(1)); + try { + account = Long.parseLong(accountStr); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + + contact = decodeURLSegment(url.getPathSegments().get(2)); + appendWhere(whereClause, Imps.Messages.THREAD_ID, "=", + getContactId(db, accountStr, contact)); + + notifyMessagesByContactContentUri = true; + break; + + case MATCH_OTR_MESSAGES_BY_THREAD_ID: + tableToChange = TABLE_IN_MEMORY_MESSAGES; + + try { + threadId = Long.parseLong(decodeURLSegment(url.getPathSegments().get(1))); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException(); + } + + appendWhere(whereClause, Imps.Messages.THREAD_ID, "=", threadId); + + notifyMessagesByThreadIdContentUri = true; + break; + + case MATCH_OTR_MESSAGE: + tableToChange = TABLE_IN_MEMORY_MESSAGES; + changedItemId = url.getPathSegments().get(1); + notifyMessagesContentUri = true; + break; + + case MATCH_AVATARS: + tableToChange = TABLE_AVATARS; + break; + + case MATCH_AVATAR: + tableToChange = TABLE_AVATARS; + changedItemId = url.getPathSegments().get(1); + break; + + case MATCH_AVATAR_BY_PROVIDER: + tableToChange = TABLE_AVATARS; + changedItemId = url.getPathSegments().get(2); + idColumnName = Imps.Avatars.ACCOUNT; + break; + + case MATCH_CHATS: + tableToChange = TABLE_CHATS; + break; + + case MATCH_CHATS_ID: + tableToChange = TABLE_CHATS; + changedItemId = url.getPathSegments().get(1); + idColumnName = Imps.Chats.CONTACT_ID; + break; + + case MATCH_PRESENCE: + //if (DBG) log("update presence: where='" + userWhere + "'"); + tableToChange = TABLE_PRESENCE; + break; + + case MATCH_PRESENCE_ID: + tableToChange = TABLE_PRESENCE; + changedItemId = url.getPathSegments().get(1); + idColumnName = Imps.Presence.CONTACT_ID; + break; + + case MATCH_PRESENCE_BULK: + count = updateBulkPresence(values, userWhere, whereArgs); + // notify change using the "content://im/contacts" url, + // so the change will be observed by listeners interested + // in contacts changes. + if (count > 0) { + getContext().getContentResolver().notifyChange(Imps.Contacts.CONTENT_URI, null); + } + + return count; + + case MATCH_INVITATION: + tableToChange = TABLE_INVITATIONS; + changedItemId = url.getPathSegments().get(1); + break; + + case MATCH_SESSIONS: + tableToChange = TABLE_SESSION_COOKIES; + break; + + case MATCH_PROVIDER_SETTINGS_BY_ID_AND_NAME: + tableToChange = TABLE_PROVIDER_SETTINGS; + + String providerId = url.getPathSegments().get(1); + String name = url.getPathSegments().get(2); + + if (values.containsKey(Imps.ProviderSettings.PROVIDER) || + values.containsKey(Imps.ProviderSettings.NAME)) { + throw new SecurityException("Cannot override the value for provider|name"); + } + + appendWhere(whereClause, Imps.ProviderSettings.PROVIDER, "=", providerId); + appendWhere(whereClause, Imps.ProviderSettings.NAME, "=", name); + + break; + + case MATCH_OUTGOING_RMQ_MESSAGES: + tableToChange = TABLE_OUTGOING_RMQ_MESSAGES; + break; + + case MATCH_LAST_RMQ_ID: + tableToChange = TABLE_LAST_RMQ_ID; + break; + + case MATCH_S2D_RMQ_IDS: + tableToChange = TABLE_S2D_RMQ_IDS; + break; + + default: + throw new UnsupportedOperationException("Cannot update URL: " + url); + } + + if (idColumnName == null) { + idColumnName = "_id"; + } + if(changedItemId != null) { + appendWhere(whereClause, idColumnName, "=", changedItemId); + } + + if (DBG) log("update " + url + " WHERE " + whereClause); + + count = db.update(tableToChange, values, whereClause.toString(), whereArgs); + + if (count > 0) { + ContentResolver resolver = getContext().getContentResolver(); + + // In most case, we query contacts with presence and chats joined, thus + // we should also notify that contacts changes when presence or chats changed. + if (match == MATCH_CHATS || match == MATCH_CHATS_ID + || match == MATCH_PRESENCE || match == MATCH_PRESENCE_ID + || match == MATCH_CONTACTS_BAREBONE) { + resolver.notifyChange(Imps.Contacts.CONTENT_URI, null); + } + + if (notifyMessagesContentUri) { + if (DBG) log("notify change for " + Imps.Messages.CONTENT_URI); + resolver.notifyChange(Imps.Messages.CONTENT_URI, null); + } + + if (notifyMessagesByContactContentUri) { + resolver.notifyChange(Imps.Messages.CONTENT_URI, null); + resolver.notifyChange(Imps.Messages.getContentUriByContact(account, contact), null); + } + + if (notifyMessagesByThreadIdContentUri) { + resolver.notifyChange(Imps.Messages.CONTENT_URI, null); + resolver.notifyChange(Imps.Messages.getContentUriByThreadId(threadId), null); + } + + if (notifyContactListContentUri) { + resolver.notifyChange(Imps.ContactList.CONTENT_URI, null); + } + + if (notifyProviderAccountContentUri) { + if (DBG) log("notify change for " + Imps.Provider.CONTENT_URI_WITH_ACCOUNT); + resolver.notifyChange(Imps.Provider.CONTENT_URI_WITH_ACCOUNT, null); + } + } + + return count; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) + throws FileNotFoundException { + return openFileHelper(uri, mode); + } + + private static void appendWhere(StringBuilder where, String columnName, + String condition, Object value) { + if (where.length() > 0) { + where.append(" AND "); + } + where.append(columnName).append(condition); + if(value != null) { + DatabaseUtils.appendValueToSql(where, value); + } + } + + private static void appendWhere(StringBuilder where, String clause) { + if (where.length() > 0) { + where.append(" AND "); + } + where.append(clause); + } + + private static String decodeURLSegment(String segment) { + try { + return URLDecoder.decode(segment, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // impossible + return segment; + } + } + + static void log(String message) { + Log.d(LOG_TAG, message); + } +} diff --git a/src/com/android/im/receiver/ImServiceAutoStarter.java b/src/com/android/im/receiver/ImServiceAutoStarter.java index 7f1992f..519e0f3 100644 --- a/src/com/android/im/receiver/ImServiceAutoStarter.java +++ b/src/com/android/im/receiver/ImServiceAutoStarter.java @@ -17,13 +17,13 @@ package com.android.im.receiver; +import com.android.im.provider.Imps; import com.android.im.service.ImServiceConstants; import android.content.Context; import android.content.Intent; import android.content.BroadcastReceiver; import android.database.Cursor; -import android.provider.Im; import android.util.Log; public class ImServiceAutoStarter extends BroadcastReceiver { @@ -34,10 +34,10 @@ public class ImServiceAutoStarter extends BroadcastReceiver { // Received intent only when the system boot is completed Log.d(TAG, "onReceiveIntent"); - String selection = Im.Account.KEEP_SIGNED_IN + "=1 AND " - + Im.Account.ACTIVE + "=1"; - Cursor cursor = context.getContentResolver().query(Im.Account.CONTENT_URI, - new String[]{Im.Account._ID}, selection, null, null); + String selection = Imps.Account.KEEP_SIGNED_IN + "=1 AND " + + Imps.Account.ACTIVE + "=1"; + Cursor cursor = context.getContentResolver().query(Imps.Account.CONTENT_URI, + new String[]{Imps.Account._ID}, selection, null, null); if (cursor != null) { if (cursor.getCount() > 0) { Log.d(TAG, "start service"); diff --git a/src/com/android/im/service/AndroidHeartBeatService.java b/src/com/android/im/service/AndroidHeartBeatService.java index fe7aa29..659451f 100644 --- a/src/com/android/im/service/AndroidHeartBeatService.java +++ b/src/com/android/im/service/AndroidHeartBeatService.java @@ -17,7 +17,8 @@ package com.android.im.service; -import com.android.im.engine.HeartbeatService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import android.app.AlarmManager; import android.app.PendingIntent; @@ -31,6 +32,8 @@ import android.os.PowerManager; import android.os.SystemClock; import android.util.SparseArray; +import com.android.im.engine.HeartbeatService; + public class AndroidHeartBeatService extends BroadcastReceiver implements HeartbeatService { @@ -43,16 +46,18 @@ public class AndroidHeartBeatService extends BroadcastReceiver private static final String HEARTBEAT_CONTENT_TYPE = "vnd.android.im/heartbeat"; - private Context mContext; - private AlarmManager mAlarmManager; - private PowerManager.WakeLock mWakeLock; + private static final ExecutorService sExecutor = Executors.newSingleThreadExecutor(); + + private final Context mContext; + private final AlarmManager mAlarmManager; + /*package*/ PowerManager.WakeLock mWakeLock; static class Alarm { public PendingIntent mAlaramSender; public Callback mCallback; } - private SparseArray mAlarms; + private final SparseArray mAlarms; public AndroidHeartBeatService(Context context) { mContext = context; @@ -72,7 +77,8 @@ public class AndroidHeartBeatService extends BroadcastReceiver int id = nextId(); alarm.mCallback = callback; Uri data = ContentUris.withAppendedId(HEARTBEAT_CONTENT_URI, id); - Intent i = new Intent().setDataAndType(data, HEARTBEAT_CONTENT_TYPE); + Intent i = new Intent(HEARTBEAT_INTENT_ACTION) + .setDataAndType(data, HEARTBEAT_CONTENT_TYPE); alarm.mAlaramSender = PendingIntent.getBroadcast(mContext, 0, i, 0); if (mAlarms.size() == 0) { mContext.registerReceiver(this, IntentFilter.create( @@ -99,22 +105,34 @@ public class AndroidHeartBeatService extends BroadcastReceiver @Override public void onReceive(Context context, Intent intent) { - mWakeLock.acquire(); - try { - int id = (int)ContentUris.parseId(intent.getData()); - Alarm alarm = mAlarms.get(id); - if (alarm == null) { - return; - } - Callback callback = alarm.mCallback; - long nextSchedule = callback.sendHeartbeat(); - if (nextSchedule <= 0) { - cancelAlarm(alarm); - } else { - setAlarm(alarm, nextSchedule); + int id = (int)ContentUris.parseId(intent.getData()); + Alarm alarm = mAlarms.get(id); + if (alarm == null) { + return; + } + sExecutor.execute(new Worker(alarm)); + } + + private class Worker implements Runnable { + private final Alarm mAlarm; + + public Worker(Alarm alarm) { + mAlarm = alarm; + } + + public void run() { + mWakeLock.acquire(); + try { + Callback callback = mAlarm.mCallback; + long nextSchedule = callback.sendHeartbeat(); + if (nextSchedule <= 0) { + cancelAlarm(mAlarm); + } else { + setAlarm(mAlarm, nextSchedule); + } + } finally { + mWakeLock.release(); } - } finally { - mWakeLock.release(); } } @@ -128,13 +146,13 @@ public class AndroidHeartBeatService extends BroadcastReceiver return null; } - private void setAlarm(Alarm alarm, long offset) { + /*package*/ synchronized void setAlarm(Alarm alarm, long offset) { long triggerAtTime = SystemClock.elapsedRealtime() + offset; mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, alarm.mAlaramSender); } - private void cancelAlarm(Alarm alarm) { + /*package*/ synchronized void cancelAlarm(Alarm alarm) { mAlarmManager.cancel(alarm.mAlaramSender); int index = mAlarms.indexOfValue(alarm); if (index >= 0) { diff --git a/src/com/android/im/service/AndroidSmsService.java b/src/com/android/im/service/AndroidSmsService.java index ddcb24e..9a32e1c 100644 --- a/src/com/android/im/service/AndroidSmsService.java +++ b/src/com/android/im/service/AndroidSmsService.java @@ -49,6 +49,7 @@ public class AndroidSmsService implements SmsService { private Context mContext; private SmsReceiver mSmsReceiver; private IntentFilter mIntentFilter; + private boolean mStarted; /*package*/HashMap mListeners; /*package*/HashMap mFailureCallbacks; @@ -110,11 +111,17 @@ public class AndroidSmsService implements SmsService { if (l == null) { l = new ListenerList(port); mListeners.put(port, l); + if (Log.isLoggable(TAG, Log.DEBUG)) { + log("Register SMS receiver on port " + port); + } // We didn't listen on the port yet, register the receiver with the // additional port. mIntentFilter.addDataAuthority("*", String.valueOf(port)); mContext.registerReceiver(mSmsReceiver, mIntentFilter); + synchronized (this) { + mStarted = true; + } } l.addListener(from, listener); } @@ -130,8 +137,11 @@ public class AndroidSmsService implements SmsService { } } - public void stop() { - mContext.unregisterReceiver(mSmsReceiver); + public synchronized void stop() { + if (mStarted) { + mContext.unregisterReceiver(mSmsReceiver); + mStarted = false; + } } private static long sNextMsgId = 0; @@ -166,6 +176,11 @@ public class AndroidSmsService implements SmsService { } else if (DATA_SMS_RECEIVED_ACTION.equals(intent.getAction())){ Uri uri = intent.getData(); int port = uri.getPort(); + + if (Log.isLoggable(TAG, Log.DEBUG)) { + log("Received sms on port:" + port); + } + ListenerList listeners = mListeners.get(port); if (listeners == null) { if (Log.isLoggable(TAG, Log.DEBUG)) { @@ -219,7 +234,10 @@ public class AndroidSmsService implements SmsService { public void notifySms(String addr, byte[] data) { int N = mListenerList.size(); for (int i = 0; i < N; i++) { - if (PhoneNumberUtils.compare(addr, mAddrList.get(i))) { + String listenAddr = mAddrList.get(i); + if (ANY_ADDRESS.equals(listenAddr) + || addr.equals(listenAddr) + || PhoneNumberUtils.compare(addr, listenAddr)) { mListenerList.get(i).onIncomingSms(data); } } diff --git a/src/com/android/im/service/ChatSessionAdapter.java b/src/com/android/im/service/ChatSessionAdapter.java index 35c3825..3ac6c60 100644 --- a/src/com/android/im/service/ChatSessionAdapter.java +++ b/src/com/android/im/service/ChatSessionAdapter.java @@ -31,6 +31,7 @@ import com.android.im.engine.ImErrorInfo; import com.android.im.engine.Message; import com.android.im.engine.MessageListener; import com.android.im.engine.Presence; +import com.android.im.provider.Imps; import android.content.ContentResolver; import android.content.ContentValues; @@ -40,7 +41,6 @@ import android.net.Uri; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.provider.BaseColumns; -import android.provider.Im; import android.util.Log; import java.util.ArrayList; @@ -49,9 +49,9 @@ import java.util.List; public class ChatSessionAdapter extends IChatSession.Stub { - private static final String NON_CHAT_MESSAGE_SELECTION = Im.BaseMessageColumns.TYPE - + "!=" + Im.MessageType.INCOMING + " AND " + Im.BaseMessageColumns.TYPE - + "!=" + Im.MessageType.OUTGOING; + private static final String NON_CHAT_MESSAGE_SELECTION = Imps.Messages.TYPE + + "!=" + Imps.MessageType.INCOMING + " AND " + Imps.Messages.TYPE + + "!=" + Imps.MessageType.OUTGOING; static final String TAG = RemoteImService.TAG; @@ -106,9 +106,8 @@ public class ChatSessionAdapter extends IChatSession.Stub { mIsGroupChat = true; long groupId = insertGroupContactInDb(group); group.addMemberListener(mListenerAdapter); - mMessageURI = ContentUris.withAppendedId( - Im.GroupMessages.CONTENT_URI_GROUP_MESSAGES_BY, groupId); - mChatURI = ContentUris.withAppendedId(Im.Chats.CONTENT_URI, groupId); + mMessageURI = Imps.Messages.getContentUriByThreadId(groupId); + mChatURI = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, groupId); insertOrUpdateChat(null); for (Contact c : group.getMembers()) { @@ -122,11 +121,8 @@ public class ChatSessionAdapter extends IChatSession.Stub { (ContactListManagerAdapter) mConnection.getContactListManager(); long contactId = listManager.queryOrInsertContact(contact); - long provider = mConnection.getProviderId(); - long account = mConnection.getAccountId(); - String address = contact.getAddress().getFullName(); - mMessageURI = Im.Messages.getContentUriByContact(provider, account, address); - mChatURI = ContentUris.withAppendedId(Im.Chats.CONTENT_URI, contactId); + mMessageURI = Imps.Messages.getContentUriByThreadId(contactId); + mChatURI = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, contactId); insertOrUpdateChat(null); mContactStatusMap.put(contact.getName(), contact.getPresence().getStatus()); @@ -215,10 +211,9 @@ public class ChatSessionAdapter extends IChatSession.Stub { public void leave() { if (mIsGroupChat) { getGroupManager().leaveChatGroupAsync((ChatGroup)mAdaptee.getParticipant()); - mContentResolver.delete(mMessageURI, null, null); - } else { - mContentResolver.delete(mMessageURI, null, null); } + + mContentResolver.delete(mMessageURI, null, null); mContentResolver.delete(mChatURI, null, null); mStatusBarNotifier.dismissChatNotification( mConnection.getProviderId(), getAddress()); @@ -234,27 +229,27 @@ public class ChatSessionAdapter extends IChatSession.Stub { public void sendMessage(String text) { if (mConnection.getState() == ImConnection.SUSPENDED) { // connection has been suspended, save the message without send it - insertMessageInDb(null, text, -1, Im.MessageType.POSTPONED); + insertMessageInDb(null, text, -1, Imps.MessageType.POSTPONED); return; } Message msg = new Message(text); mAdaptee.sendMessageAsync(msg); long now = System.currentTimeMillis(); - insertMessageInDb(null, text, now, Im.MessageType.OUTGOING); + insertMessageInDb(null, text, now, Imps.MessageType.OUTGOING); } void sendPostponedMessages() { String[] projection = new String[] { BaseColumns._ID, - Im.BaseMessageColumns.BODY, - Im.BaseMessageColumns.DATE, - Im.BaseMessageColumns.TYPE, + Imps.Messages.BODY, + Imps.Messages.DATE, + Imps.Messages.TYPE, }; - String selection = Im.BaseMessageColumns.TYPE + "=?"; + String selection = "messages.type=?"; Cursor c = mContentResolver.query(mMessageURI, projection, selection, - new String[]{Integer.toString(Im.MessageType.POSTPONED)}, null); + new String[]{Integer.toString(Imps.MessageType.POSTPONED)}, null); if (c == null) { Log.e(TAG, "Query error while querying postponed messages"); return; @@ -265,7 +260,7 @@ public class ChatSessionAdapter extends IChatSession.Stub { mAdaptee.sendMessageAsync(new Message(body)); c.updateLong(2, System.currentTimeMillis()); - c.updateInt(3, Im.MessageType.OUTGOING); + c.updateInt(3, Imps.MessageType.OUTGOING); } c.commitUpdates(); c.close(); @@ -286,7 +281,7 @@ public class ChatSessionAdapter extends IChatSession.Stub { public void markAsRead() { if (mHasUnreadMessages) { ContentValues values = new ContentValues(1); - values.put(Im.Chats.LAST_UNREAD_MESSAGE, (String) null); + values.put(Imps.Chats.LAST_UNREAD_MESSAGE, (String) null); mConnection.getContext().getContentResolver().update(mChatURI, values, null, null); mStatusBarNotifier.dismissChatNotification(mConnection.getProviderId(), getAddress()); @@ -340,15 +335,15 @@ public class ChatSessionAdapter extends IChatSession.Stub { String contact = incoming ? oldParticipant.getName() : null; long time = msg.getDateTime().getTime(); insertMessageInDb(contact, msg.getBody(), time, - incoming ? Im.MessageType.INCOMING : Im.MessageType.OUTGOING); + incoming ? Imps.MessageType.INCOMING : Imps.MessageType.OUTGOING); } } void insertOrUpdateChat(String message) { ContentValues values = new ContentValues(2); - values.put(Im.Chats.LAST_MESSAGE_DATE, System.currentTimeMillis()); - values.put(Im.Chats.LAST_UNREAD_MESSAGE, message); + values.put(Imps.Chats.LAST_MESSAGE_DATE, System.currentTimeMillis()); + values.put(Imps.Chats.LAST_UNREAD_MESSAGE, message); // ImProvider.insert() will replace the chat if it already exist. mContentResolver.insert(mChatURI, values); } @@ -356,13 +351,13 @@ public class ChatSessionAdapter extends IChatSession.Stub { private long insertGroupContactInDb(ChatGroup group) { // Insert a record in contacts table ContentValues values = new ContentValues(4); - values.put(Im.Contacts.USERNAME, group.getAddress().getFullName()); - values.put(Im.Contacts.NICKNAME, group.getName()); - values.put(Im.Contacts.CONTACTLIST, ContactListManagerAdapter.FAKE_TEMPORARY_LIST_ID); - values.put(Im.Contacts.TYPE, Im.Contacts.TYPE_GROUP); + values.put(Imps.Contacts.USERNAME, group.getAddress().getFullName()); + values.put(Imps.Contacts.NICKNAME, group.getName()); + values.put(Imps.Contacts.CONTACTLIST, ContactListManagerAdapter.FAKE_TEMPORARY_LIST_ID); + values.put(Imps.Contacts.TYPE, Imps.Contacts.TYPE_GROUP); Uri contactUri = ContentUris.withAppendedId(ContentUris.withAppendedId( - Im.Contacts.CONTENT_URI, mConnection.mProviderId), mConnection.mAccountId); + Imps.Contacts.CONTENT_URI, mConnection.mProviderId), mConnection.mAccountId); long id = ContentUris.parseId(mContentResolver.insert(contactUri, values)); ArrayList memberValues = new ArrayList(); @@ -370,9 +365,9 @@ public class ChatSessionAdapter extends IChatSession.Stub { for (Contact member : group.getMembers()) { if (!member.equals(self)) { // avoid to insert the user himself ContentValues memberValue = new ContentValues(2); - memberValue.put(Im.GroupMembers.USERNAME, + memberValue.put(Imps.GroupMembers.USERNAME, member.getAddress().getFullName()); - memberValue.put(Im.GroupMembers.NICKNAME, + memberValue.put(Imps.GroupMembers.NICKNAME, member.getName()); memberValues.add(memberValue); } @@ -380,7 +375,7 @@ public class ChatSessionAdapter extends IChatSession.Stub { if (!memberValues.isEmpty()) { ContentValues[] result = new ContentValues[memberValues.size()]; memberValues.toArray(result); - Uri memberUri = ContentUris.withAppendedId(Im.GroupMembers.CONTENT_URI, id); + Uri memberUri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, id); mContentResolver.bulkInsert(memberUri, result); } return id; @@ -388,27 +383,27 @@ public class ChatSessionAdapter extends IChatSession.Stub { void insertGroupMemberInDb(Contact member) { ContentValues values1 = new ContentValues(2); - values1.put(Im.GroupMembers.USERNAME, member.getAddress().getFullName()); - values1.put(Im.GroupMembers.NICKNAME, member.getName()); + values1.put(Imps.GroupMembers.USERNAME, member.getAddress().getFullName()); + values1.put(Imps.GroupMembers.NICKNAME, member.getName()); ContentValues values = values1; long groupId = ContentUris.parseId(mChatURI); - Uri uri = ContentUris.withAppendedId(Im.GroupMembers.CONTENT_URI, groupId); + Uri uri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, groupId); mContentResolver.insert(uri, values); insertMessageInDb(member.getName(), null, System.currentTimeMillis(), - Im.MessageType.PRESENCE_AVAILABLE); + Imps.MessageType.PRESENCE_AVAILABLE); } void deleteGroupMemberInDb(Contact member) { - String where = Im.GroupMembers.USERNAME + "=?"; + String where = Imps.GroupMembers.USERNAME + "=?"; String[] selectionArgs = { member.getAddress().getFullName() }; long groupId = ContentUris.parseId(mChatURI); - Uri uri = ContentUris.withAppendedId(Im.GroupMembers.CONTENT_URI, groupId); + Uri uri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, groupId); mContentResolver.delete(uri, where, selectionArgs); insertMessageInDb(member.getName(), null, System.currentTimeMillis(), - Im.MessageType.PRESENCE_UNAVAILABLE); + Imps.MessageType.PRESENCE_UNAVAILABLE); } void insertPresenceUpdatesMsg(String contact, Presence presence) { @@ -425,20 +420,20 @@ public class ChatSessionAdapter extends IChatSession.Stub { int messageType; switch (status) { case Presence.AVAILABLE: - messageType = Im.MessageType.PRESENCE_AVAILABLE; + messageType = Imps.MessageType.PRESENCE_AVAILABLE; break; case Presence.AWAY: case Presence.IDLE: - messageType = Im.MessageType.PRESENCE_AWAY; + messageType = Imps.MessageType.PRESENCE_AWAY; break; case Presence.DO_NOT_DISTURB: - messageType = Im.MessageType.PRESENCE_DND; + messageType = Imps.MessageType.PRESENCE_DND; break; default: - messageType = Im.MessageType.PRESENCE_UNAVAILABLE; + messageType = Imps.MessageType.PRESENCE_UNAVAILABLE; break; } @@ -450,7 +445,7 @@ public class ChatSessionAdapter extends IChatSession.Stub { } void removeMessageInDb(int type) { - mContentResolver.delete(mMessageURI, Im.BaseMessageColumns.TYPE + "=?", + mContentResolver.delete(mMessageURI, Imps.Messages.TYPE + "=?", new String[]{Integer.toString(type)}); } @@ -460,12 +455,13 @@ public class ChatSessionAdapter extends IChatSession.Stub { Uri insertMessageInDb(String contact, String body, long time, int type, int errCode) { ContentValues values = new ContentValues(mIsGroupChat ? 4 : 3); - values.put(Im.BaseMessageColumns.BODY, body); - values.put(Im.BaseMessageColumns.DATE, time); - values.put(Im.BaseMessageColumns.TYPE, type); - values.put(Im.BaseMessageColumns.ERROR_CODE, errCode); + values.put(Imps.Messages.BODY, body); + values.put(Imps.Messages.DATE, time); + values.put(Imps.Messages.TYPE, type); + values.put(Imps.Messages.ERROR_CODE, errCode); if (mIsGroupChat) { - values.put(Im.BaseMessageColumns.CONTACT, contact); + values.put(Imps.Messages.NICKNAME, contact); + values.put(Imps.Messages.IS_GROUP_CHAT, 1); } return mContentResolver.insert(mMessageURI, values); @@ -483,7 +479,7 @@ public class ChatSessionAdapter extends IChatSession.Stub { } else { insertOrUpdateChat(body); } - insertMessageInDb(nickname, body, time, Im.MessageType.INCOMING); + insertMessageInDb(nickname, body, time, Imps.MessageType.INCOMING); int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { @@ -506,7 +502,7 @@ public class ChatSessionAdapter extends IChatSession.Stub { public void onSendMessageError(ChatSession ses, final Message msg, final ImErrorInfo error) { insertMessageInDb(null, null, System.currentTimeMillis(), - Im.MessageType.OUTGOING, error.getCode()); + Imps.MessageType.OUTGOING, error.getCode()); final int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { diff --git a/src/com/android/im/service/ContactListManagerAdapter.java b/src/com/android/im/service/ContactListManagerAdapter.java index eaa0a48..47c1615 100644 --- a/src/com/android/im/service/ContactListManagerAdapter.java +++ b/src/com/android/im/service/ContactListManagerAdapter.java @@ -33,7 +33,6 @@ import android.database.Cursor; import android.net.Uri; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.provider.Im; import android.util.Log; import android.widget.Toast; @@ -52,6 +51,7 @@ import com.android.im.engine.ImErrorInfo; import com.android.im.engine.ImException; import com.android.im.engine.Presence; import com.android.im.engine.SubscriptionRequestListener; +import com.android.im.provider.Imps; public class ContactListManagerAdapter extends IContactListManager.Stub { static final String TAG = RemoteImService.TAG; @@ -82,7 +82,7 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { private Uri mContactUrl; static final long FAKE_TEMPORARY_LIST_ID = -1; - static final String[] CONTACT_LIST_ID_PROJECTION = { Im.ContactList._ID }; + static final String[] CONTACT_LIST_ID_PROJECTION = { Imps.ContactList._ID }; RemoteImService mContext; @@ -106,13 +106,13 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { mAccountId = mConn.getAccountId(); mProviderId = mConn.getProviderId(); - Uri.Builder builder = Im.Avatars.CONTENT_URI_AVATARS_BY.buildUpon(); + Uri.Builder builder = Imps.Avatars.CONTENT_URI_AVATARS_BY.buildUpon(); ContentUris.appendId(builder, mProviderId); ContentUris.appendId(builder, mAccountId); mAvatarUrl = builder.build(); - builder = Im.Contacts.CONTENT_URI_CONTACTS_BY.buildUpon(); + builder = Imps.Contacts.CONTENT_URI_CONTACTS_BY.buildUpon(); ContentUris.appendId(builder, mProviderId); ContentUris.appendId(builder, mAccountId); @@ -151,7 +151,7 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { // database. closeChatSession(address); - String selection = Im.Contacts.USERNAME + "=?"; + String selection = Imps.Contacts.USERNAME + "=?"; String[] selectionArgs = { address }; mResolver.delete(mContactUrl, selection, selectionArgs); synchronized (mTemporaryContacts) { @@ -274,9 +274,9 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { long result; String username = c.getAddress().getFullName(); - String selection = Im.Contacts.USERNAME + "=?"; + String selection = Imps.Contacts.USERNAME + "=?"; String[] selectionArgs = { username }; - String[] projection = {Im.Contacts._ID}; + String[] projection = {Imps.Contacts._ID}; Cursor cursor = mResolver.query(mContactUrl, projection, selection, selectionArgs, null); @@ -370,13 +370,13 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { private void removeObsoleteContactsAndLists() { // remove all contacts for this provider & account which have not been // added since login, yet still exist in db from a prior login - Exclusion exclusion = new Exclusion(Im.Contacts.USERNAME, mValidatedContacts); + Exclusion exclusion = new Exclusion(Imps.Contacts.USERNAME, mValidatedContacts); mResolver.delete(mContactUrl, exclusion.getSelection(), exclusion.getSelectionArgs()); // remove all blocked contacts for this provider & account which have not been // added since login, yet still exist in db from a prior login - exclusion = new Exclusion(Im.BlockedList.USERNAME, mValidatedBlockedContacts); - Uri.Builder builder = Im.BlockedList.CONTENT_URI.buildUpon(); + exclusion = new Exclusion(Imps.BlockedList.USERNAME, mValidatedBlockedContacts); + Uri.Builder builder = Imps.BlockedList.CONTENT_URI.buildUpon(); ContentUris.appendId(builder, mProviderId); ContentUris.appendId(builder, mAccountId); Uri uri = builder.build(); @@ -384,8 +384,8 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { // remove all contact lists for this provider & account which have not been // added since login, yet still exist in db from a prior login - exclusion = new Exclusion(Im.ContactList.NAME, mValidatedContactLists); - builder = Im.ContactList.CONTENT_URI.buildUpon(); + exclusion = new Exclusion(Imps.ContactList.NAME, mValidatedContactLists); + builder = Imps.ContactList.CONTENT_URI.buildUpon(); ContentUris.appendId(builder, mProviderId); ContentUris.appendId(builder, mAccountId); uri = builder.build(); @@ -514,7 +514,7 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { case CONTACT_BLOCKED: insertBlockedContactToDataBase(contact); address = contact.getAddress().getFullName(); - updateContactType(address, Im.Contacts.TYPE_BLOCKED); + updateContactType(address, Imps.Contacts.TYPE_BLOCKED); closeChatSession(address); notificationText = mContext.getResources().getString( R.string.block_contact_success, contact.getName()); @@ -613,8 +613,8 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { String username = from.getAddress().getFullName(); String nickname = from.getName(); Uri uri = insertOrUpdateSubscription(username, nickname, - Im.Contacts.SUBSCRIPTION_TYPE_FROM, - Im.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING); + Imps.Contacts.SUBSCRIPTION_TYPE_FROM, + Imps.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING); mContext.getStatusBarNotifier().notifySubscriptionRequest(mProviderId, mAccountId, ContentUris.parseId(uri), username, nickname); final int N = mRemoteSubscriptionListeners.beginBroadcast(); @@ -633,8 +633,8 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { public void onSubscriptionApproved(final String contact) { insertOrUpdateSubscription(contact, null, - Im.Contacts.SUBSCRIPTION_TYPE_NONE, - Im.Contacts.SUBSCRIPTION_STATUS_NONE); + Imps.Contacts.SUBSCRIPTION_TYPE_NONE, + Imps.Contacts.SUBSCRIPTION_STATUS_NONE); final int N = mRemoteSubscriptionListeners.beginBroadcast(); for (int i = 0; i < N; i++) { @@ -652,8 +652,8 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { public void onSubscriptionDeclined(final String contact) { insertOrUpdateSubscription(contact, null, - Im.Contacts.SUBSCRIPTION_TYPE_NONE, - Im.Contacts.SUBSCRIPTION_STATUS_NONE); + Imps.Contacts.SUBSCRIPTION_TYPE_NONE, + Imps.Contacts.SUBSCRIPTION_STATUS_NONE); final int N = mRemoteSubscriptionListeners.beginBroadcast(); for (int i = 0; i < N; i++) { @@ -694,15 +694,15 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { // handle the odd case where a blocked contact's nickname has changed removeBlockedContactFromDataBase(contact); - Uri.Builder builder = Im.BlockedList.CONTENT_URI.buildUpon(); + Uri.Builder builder = Imps.BlockedList.CONTENT_URI.buildUpon(); ContentUris.appendId(builder, mProviderId); ContentUris.appendId(builder, mAccountId); Uri uri = builder.build(); String username = contact.getAddress().getFullName(); ContentValues values = new ContentValues(2); - values.put(Im.BlockedList.USERNAME, username); - values.put(Im.BlockedList.NICKNAME, contact.getName()); + values.put(Imps.BlockedList.USERNAME, username); + values.put(Imps.BlockedList.NICKNAME, contact.getName()); mResolver.insert(uri, values); @@ -712,15 +712,15 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { void removeBlockedContactFromDataBase(Contact contact) { String address = contact.getAddress().getFullName(); - Uri.Builder builder = Im.BlockedList.CONTENT_URI.buildUpon(); + Uri.Builder builder = Imps.BlockedList.CONTENT_URI.buildUpon(); ContentUris.appendId(builder, mProviderId); ContentUris.appendId(builder, mAccountId); Uri uri = builder.build(); - mResolver.delete(uri, Im.BlockedList.USERNAME + "=?", new String[]{ address }); + mResolver.delete(uri, Imps.BlockedList.USERNAME + "=?", new String[]{ address }); - int type = isTemporary(address) ? Im.Contacts.TYPE_TEMPORARY - : Im.Contacts.TYPE_NORMAL; + int type = isTemporary(address) ? Imps.Contacts.TYPE_TEMPORARY + : Imps.Contacts.TYPE_NORMAL; updateContactType(address, type); } @@ -729,11 +729,11 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { mTemporaryContacts.remove(address); } ContentValues values = new ContentValues(2); - values.put(Im.Contacts.TYPE, Im.Contacts.TYPE_NORMAL); - values.put(Im.Contacts.CONTACTLIST, listId); + values.put(Imps.Contacts.TYPE, Imps.Contacts.TYPE_NORMAL); + values.put(Imps.Contacts.CONTACTLIST, listId); - String selection = Im.Contacts.USERNAME + "=? AND " + Im.Contacts.TYPE + "=" - + Im.Contacts.TYPE_TEMPORARY; + String selection = Imps.Contacts.USERNAME + "=? AND " + Imps.Contacts.TYPE + "=" + + Imps.Contacts.TYPE_TEMPORARY; String[] selectionArgs = { address }; mResolver.update(mContactUrl, values, selection, selectionArgs); @@ -741,7 +741,7 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { void updateContactType(String address, int type) { ContentValues values = new ContentValues(1); - values.put(Im.Contacts.TYPE, type); + values.put(Imps.Contacts.TYPE, type); updateContact(address, values); } @@ -755,8 +755,8 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { */ Uri insertOrUpdateSubscription(String username, String nickname, int subscriptionType, int subscriptionStatus) { - Cursor cursor = mResolver.query(mContactUrl, new String[]{ Im.Contacts._ID }, - Im.Contacts.USERNAME + "=?", new String[]{username}, null); + Cursor cursor = mResolver.query(mContactUrl, new String[]{ Imps.Contacts._ID }, + Imps.Contacts.USERNAME + "=?", new String[]{username}, null); if (cursor == null) { Log.w(TAG, "query contact " + username + " failed"); return null; @@ -765,20 +765,20 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { Uri uri; if (cursor.moveToFirst()) { ContentValues values = new ContentValues(2); - values.put(Im.Contacts.SUBSCRIPTION_TYPE, subscriptionType); - values.put(Im.Contacts.SUBSCRIPTION_STATUS, subscriptionStatus); + values.put(Imps.Contacts.SUBSCRIPTION_TYPE, subscriptionType); + values.put(Imps.Contacts.SUBSCRIPTION_STATUS, subscriptionStatus); long contactId = cursor.getLong(0); - uri = ContentUris.withAppendedId(Im.Contacts.CONTENT_URI, contactId); + uri = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, contactId); mResolver.update(uri, values, null, null); } else { ContentValues values = new ContentValues(6); - values.put(Im.Contacts.USERNAME, username); - values.put(Im.Contacts.NICKNAME, nickname); - values.put(Im.Contacts.TYPE, Im.Contacts.TYPE_NORMAL); - values.put(Im.Contacts.CONTACTLIST, FAKE_TEMPORARY_LIST_ID); - values.put(Im.Contacts.SUBSCRIPTION_TYPE, subscriptionType); - values.put(Im.Contacts.SUBSCRIPTION_STATUS, subscriptionStatus); + values.put(Imps.Contacts.USERNAME, username); + values.put(Imps.Contacts.NICKNAME, nickname); + values.put(Imps.Contacts.TYPE, Imps.Contacts.TYPE_NORMAL); + values.put(Imps.Contacts.CONTACTLIST, FAKE_TEMPORARY_LIST_ID); + values.put(Imps.Contacts.SUBSCRIPTION_TYPE, subscriptionType); + values.put(Imps.Contacts.SUBSCRIPTION_STATUS, subscriptionStatus); uri = mResolver.insert(mContactUrl, values); } @@ -787,7 +787,7 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { } void updateContact(String username, ContentValues values) { - String selection = Im.Contacts.USERNAME + "=?"; + String selection = Imps.Contacts.USERNAME + "=?"; String[] selectionArgs = { username }; mResolver.update(mContactUrl, values, selection, selectionArgs); } @@ -812,13 +812,13 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { } ContentValues values = new ContentValues(); - values.put(Im.Contacts.ACCOUNT, mAccountId); - values.putStringArrayList(Im.Contacts.USERNAME, usernames); - values.putStringArrayList(Im.Presence.PRESENCE_STATUS, statusArray); - values.putStringArrayList(Im.Presence.PRESENCE_CUSTOM_STATUS, customStatusArray); - values.putStringArrayList(Im.Presence.CONTENT_TYPE, clientTypeArray); + values.put(Imps.Contacts.ACCOUNT, mAccountId); + values.putStringArrayList(Imps.Contacts.USERNAME, usernames); + values.putStringArrayList(Imps.Presence.PRESENCE_STATUS, statusArray); + values.putStringArrayList(Imps.Presence.PRESENCE_CUSTOM_STATUS, customStatusArray); + values.putStringArrayList(Imps.Presence.CONTENT_TYPE, clientTypeArray); - mResolver.update(Im.Presence.BULK_CONTENT_URI, values, null, null); + mResolver.update(Imps.Presence.BULK_CONTENT_URI, values, null, null); } void updateAvatarsContent(Contact[] contacts) { @@ -834,8 +834,8 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { String username = contact.getAddress().getFullName(); ContentValues values = new ContentValues(2); - values.put(Im.Avatars.CONTACT, username); - values.put(Im.Avatars.DATA, avatarData); + values.put(Imps.Avatars.CONTACT, username); + values.put(Imps.Avatars.DATA, avatarData); avatars.add(values); usernames.add(username); } @@ -862,22 +862,22 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { // delete contacts of this list first mResolver.delete(mContactUrl, - Im.Contacts.CONTACTLIST + "=?", new String[]{Long.toString(id)}); + Imps.Contacts.CONTACTLIST + "=?", new String[]{Long.toString(id)}); - mResolver.delete(ContentUris.withAppendedId(Im.ContactList.CONTENT_URI, id), null, null); + mResolver.delete(ContentUris.withAppendedId(Imps.ContactList.CONTENT_URI, id), null, null); synchronized (mContactLists) { return mContactLists.remove(listAdapter.getAddress()); } } void addContactListContent(ContactList list) { - String selection = Im.ContactList.NAME + "=? AND " - + Im.ContactList.PROVIDER + "=? AND " - + Im.ContactList.ACCOUNT + "=?"; + String selection = Imps.ContactList.NAME + "=? AND " + + Imps.ContactList.PROVIDER + "=? AND " + + Imps.ContactList.ACCOUNT + "=?"; String[] selectionArgs = { list.getName(), Long.toString(mProviderId), Long.toString(mAccountId) }; - Cursor cursor = mResolver.query(Im.ContactList.CONTENT_URI, + Cursor cursor = mResolver.query(Imps.ContactList.CONTENT_URI, CONTACT_LIST_ID_PROJECTION, selection, selectionArgs, @@ -887,7 +887,7 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { try { if (cursor.moveToFirst()) { listId = cursor.getLong(0); - uri = ContentUris.withAppendedId(Im.ContactList.CONTENT_URI, listId); + uri = ContentUris.withAppendedId(Imps.ContactList.CONTENT_URI, listId); //Log.d(TAG,"Found and removing ContactList with name "+list.getName()); } } finally { @@ -896,19 +896,19 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { if (uri != null) { // remove existing ContactList and Contacts of that list for replacement by the newly // downloaded list - mResolver.delete(mContactUrl, Im.Contacts.CONTACTLIST + "=?", + mResolver.delete(mContactUrl, Imps.Contacts.CONTACTLIST + "=?", new String[]{Long.toString(listId)}); mResolver.delete(uri, selection, selectionArgs); } ContentValues contactListValues = new ContentValues(3); - contactListValues.put(Im.ContactList.NAME, list.getName()); - contactListValues.put(Im.ContactList.PROVIDER, mProviderId); - contactListValues.put(Im.ContactList.ACCOUNT, mAccountId); + contactListValues.put(Imps.ContactList.NAME, list.getName()); + contactListValues.put(Imps.ContactList.PROVIDER, mProviderId); + contactListValues.put(Imps.ContactList.ACCOUNT, mAccountId); //Log.d(TAG, "Adding ContactList name="+list.getName()); mValidatedContactLists.add(list.getName()); - uri = mResolver.insert(Im.ContactList.CONTENT_URI, contactListValues); + uri = mResolver.insert(Imps.ContactList.CONTENT_URI, contactListValues); listId = ContentUris.parseId(uri); synchronized (mContactLists) { @@ -938,12 +938,12 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { for (Contact c : contacts) { String username = c.getAddress().getFullName(); String nickname = c.getName(); - int type = Im.Contacts.TYPE_NORMAL; + int type = Imps.Contacts.TYPE_NORMAL; if(isTemporary(username)) { - type = Im.Contacts.TYPE_TEMPORARY; + type = Imps.Contacts.TYPE_TEMPORARY; } if (isBlocked(username)) { - type = Im.Contacts.TYPE_BLOCKED; + type = Imps.Contacts.TYPE_BLOCKED; } usernames.add(username); @@ -952,29 +952,29 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { } ContentValues values = new ContentValues(6); - values.put(Im.Contacts.PROVIDER, mProviderId); - values.put(Im.Contacts.ACCOUNT, mAccountId); - values.put(Im.Contacts.CONTACTLIST, listId); - values.putStringArrayList(Im.Contacts.USERNAME, usernames); - values.putStringArrayList(Im.Contacts.NICKNAME, nicknames); - values.putStringArrayList(Im.Contacts.TYPE, contactTypeArray); + values.put(Imps.Contacts.PROVIDER, mProviderId); + values.put(Imps.Contacts.ACCOUNT, mAccountId); + values.put(Imps.Contacts.CONTACTLIST, listId); + values.putStringArrayList(Imps.Contacts.USERNAME, usernames); + values.putStringArrayList(Imps.Contacts.NICKNAME, nicknames); + values.putStringArrayList(Imps.Contacts.TYPE, contactTypeArray); - mResolver.insert(Im.Contacts.BULK_CONTENT_URI, values); + mResolver.insert(Imps.Contacts.BULK_CONTENT_URI, values); } void updateListNameInDataBase(ContactList list) { ContactListAdapter listAdapter = getContactListAdapter(list.getAddress()); - Uri uri = ContentUris.withAppendedId(Im.ContactList.CONTENT_URI, listAdapter.getDataBaseId()); + Uri uri = ContentUris.withAppendedId(Imps.ContactList.CONTENT_URI, listAdapter.getDataBaseId()); ContentValues values = new ContentValues(1); - values.put(Im.ContactList.NAME, list.getName()); + values.put(Imps.ContactList.NAME, list.getName()); mResolver.update(uri, values, null, null); } void deleteContactFromDataBase(Contact contact, ContactList list) { - String selection = Im.Contacts.USERNAME - + "=? AND " + Im.Contacts.CONTACTLIST + "=?"; + String selection = Imps.Contacts.USERNAME + + "=? AND " + Imps.Contacts.CONTACTLIST + "=?"; long listId = getContactListAdapter(list.getAddress()).getDataBaseId(); String username = contact.getAddress().getFullName(); String[] selectionArgs = {username, Long.toString(listId)}; @@ -996,7 +996,7 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { ContentValues presenceValues = getPresenceValues(ContentUris.parseId(uri), contact.getPresence()); - mResolver.insert(Im.Presence.CONTENT_URI, presenceValues); + mResolver.insert(Imps.Presence.CONTENT_URI, presenceValues); return uri; } @@ -1004,34 +1004,33 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { private ContentValues getContactContentValues(Contact contact, long listId) { final String username = contact.getAddress().getFullName(); final String nickname = contact.getName(); - int type = Im.Contacts.TYPE_NORMAL; + int type = Imps.Contacts.TYPE_NORMAL; if(isTemporary(username)) { - type = Im.Contacts.TYPE_TEMPORARY; + type = Imps.Contacts.TYPE_TEMPORARY; } if (isBlocked(username)) { - type = Im.Contacts.TYPE_BLOCKED; + type = Imps.Contacts.TYPE_BLOCKED; } ContentValues values = new ContentValues(4); - values.put(Im.Contacts.USERNAME, username); - values.put(Im.Contacts.NICKNAME, nickname); - values.put(Im.Contacts.CONTACTLIST, listId); - values.put(Im.Contacts.TYPE, type); + values.put(Imps.Contacts.USERNAME, username); + values.put(Imps.Contacts.NICKNAME, nickname); + values.put(Imps.Contacts.CONTACTLIST, listId); + values.put(Imps.Contacts.TYPE, type); return values; } void clearHistoryMessages(String contact) { - Uri uri = Im.Messages.getContentUriByContact(mProviderId, - mAccountId, contact); + Uri uri = Imps.Messages.getContentUriByContact(mAccountId, contact); mResolver.delete(uri, null, null); } private ContentValues getPresenceValues(long contactId, Presence p) { ContentValues values = new ContentValues(3); - values.put(Im.Presence.CONTACT_ID, contactId); - values.put(Im.Contacts.PRESENCE_STATUS, convertPresenceStatus(p)); - values.put(Im.Contacts.PRESENCE_CUSTOM_STATUS, p.getStatusText()); - values.put(Im.Presence.CLIENT_TYPE, translateClientType(p)); + values.put(Imps.Presence.CONTACT_ID, contactId); + values.put(Imps.Contacts.PRESENCE_STATUS, convertPresenceStatus(p)); + values.put(Imps.Contacts.PRESENCE_CUSTOM_STATUS, p.getStatusText()); + values.put(Imps.Presence.CLIENT_TYPE, translateClientType(p)); return values; } @@ -1039,9 +1038,9 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { int clientType = presence.getClientType(); switch (clientType) { case Presence.CLIENT_TYPE_MOBILE: - return Im.Presence.CLIENT_TYPE_MOBILE; + return Imps.Presence.CLIENT_TYPE_MOBILE; default: - return Im.Presence.CLIENT_TYPE_DEFAULT; + return Imps.Presence.CLIENT_TYPE_DEFAULT; } } @@ -1054,24 +1053,24 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { public static int convertPresenceStatus(Presence presence) { switch (presence.getStatus()) { case Presence.AVAILABLE: - return Im.Presence.AVAILABLE; + return Imps.Presence.AVAILABLE; case Presence.IDLE: - return Im.Presence.IDLE; + return Imps.Presence.IDLE; case Presence.AWAY: - return Im.Presence.AWAY; + return Imps.Presence.AWAY; case Presence.DO_NOT_DISTURB: - return Im.Presence.DO_NOT_DISTURB; + return Imps.Presence.DO_NOT_DISTURB; case Presence.OFFLINE: - return Im.Presence.OFFLINE; + return Imps.Presence.OFFLINE; } // impossible... Log.e(TAG, "Illegal presence status value " + presence.getStatus()); - return Im.Presence.AVAILABLE; + return Imps.Presence.AVAILABLE; } public void clearOnLogout() { @@ -1099,7 +1098,7 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { * IM sessions, the temporary contacts need to be cleared after logout. */ private void clearTemporaryContacts() { - String selection = Im.Contacts.CONTACTLIST + "=" + FAKE_TEMPORARY_LIST_ID; + String selection = Imps.Contacts.CONTACTLIST + "=" + FAKE_TEMPORARY_LIST_ID; mResolver.delete(mContactUrl, selection, null); } @@ -1109,13 +1108,13 @@ public class ContactListManagerAdapter extends IContactListManager.Stub { */ void clearPresence() { StringBuilder where = new StringBuilder(); - where.append(Im.Presence.CONTACT_ID); + where.append(Imps.Presence.CONTACT_ID); where.append(" in (select _id from contacts where "); - where.append(Im.Contacts.ACCOUNT); + where.append(Imps.Contacts.ACCOUNT); where.append("="); where.append(mAccountId); where.append(")"); - mResolver.delete(Im.Presence.CONTENT_URI, where.toString(), null); + mResolver.delete(Imps.Presence.CONTENT_URI, where.toString(), null); } void closeChatSession(String address) { diff --git a/src/com/android/im/service/ImConnectionAdapter.java b/src/com/android/im/service/ImConnectionAdapter.java index 7b97ac6..e8bbc31 100644 --- a/src/com/android/im/service/ImConnectionAdapter.java +++ b/src/com/android/im/service/ImConnectionAdapter.java @@ -27,7 +27,6 @@ import android.database.Cursor; import android.net.Uri; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.provider.Im; import android.util.Log; import com.android.im.IChatSessionManager; @@ -46,13 +45,14 @@ import com.android.im.engine.Invitation; import com.android.im.engine.InvitationListener; import com.android.im.engine.LoginInfo; import com.android.im.engine.Presence; +import com.android.im.provider.Imps; public class ImConnectionAdapter extends IImConnection.Stub { private static final String TAG = RemoteImService.TAG; private static final String[] SESSION_COOKIE_PROJECTION = { - Im.SessionCookies.NAME, - Im.SessionCookies.VALUE, + Imps.SessionCookies.NAME, + Imps.SessionCookies.VALUE, }; private static final int COLUMN_SESSION_COOKIE_NAME = 0; @@ -133,7 +133,7 @@ public class ImConnectionAdapter extends IImConnection.Stub { } private Uri getSessionCookiesUri() { - Uri.Builder builder = Im.SessionCookies.CONTENT_URI_SESSION_COOKIES_BY.buildUpon(); + Uri.Builder builder = Imps.SessionCookies.CONTENT_URI_SESSION_COOKIES_BY.buildUpon(); ContentUris.appendId(builder, mProviderId); ContentUris.appendId(builder, mAccountId); @@ -258,21 +258,21 @@ public class ImConnectionAdapter extends IImConnection.Stub { return; } ContentResolver cr = mService.getContentResolver(); - Cursor c = cr.query(ContentUris.withAppendedId(Im.Invitation.CONTENT_URI, id), null, null, null, null); + Cursor c = cr.query(ContentUris.withAppendedId(Imps.Invitation.CONTENT_URI, id), null, null, null, null); if(c == null) { return; } if(c.moveToFirst()) { - String inviteId = c.getString(c.getColumnIndexOrThrow(Im.Invitation.INVITE_ID)); + String inviteId = c.getString(c.getColumnIndexOrThrow(Imps.Invitation.INVITE_ID)); int status; if(accept) { mGroupManager.acceptInvitationAsync(inviteId); - status = Im.Invitation.STATUS_ACCEPTED; + status = Imps.Invitation.STATUS_ACCEPTED; } else { mGroupManager.rejectInvitationAsync(inviteId); - status = Im.Invitation.STATUS_REJECTED; + status = Imps.Invitation.STATUS_REJECTED; } - c.updateInt(c.getColumnIndexOrThrow(Im.Invitation.STATUS), status); + c.updateInt(c.getColumnIndexOrThrow(Imps.Invitation.STATUS), status); c.commitUpdates(); } c.close(); @@ -287,8 +287,8 @@ public class ImConnectionAdapter extends IImConnection.Stub { for(Map.Entry entry : cookies.entrySet()){ ContentValues values = new ContentValues(2); - values.put(Im.SessionCookies.NAME, entry.getKey()); - values.put(Im.SessionCookies.VALUE, entry.getValue()); + values.put(Imps.SessionCookies.NAME, entry.getKey()); + values.put(Imps.SessionCookies.VALUE, entry.getValue()); valuesList[i++] = values; } @@ -302,7 +302,7 @@ public class ImConnectionAdapter extends IImConnection.Stub { void updateAccountStatusInDb() { Presence p = getUserPresence(); - int presenceStatus = Im.Presence.OFFLINE; + int presenceStatus = Imps.Presence.OFFLINE; int connectionStatus = convertConnStateForDb(mConnectionState); if (p != null) { @@ -310,12 +310,12 @@ public class ImConnectionAdapter extends IImConnection.Stub { } ContentResolver cr = mService.getContentResolver(); - Uri uri = Im.AccountStatus.CONTENT_URI; + Uri uri = Imps.AccountStatus.CONTENT_URI; ContentValues values = new ContentValues(); - values.put(Im.AccountStatus.ACCOUNT, mAccountId); - values.put(Im.AccountStatus.PRESENCE_STATUS, presenceStatus); - values.put(Im.AccountStatus.CONNECTION_STATUS, connectionStatus); + values.put(Imps.AccountStatus.ACCOUNT, mAccountId); + values.put(Imps.AccountStatus.PRESENCE_STATUS, presenceStatus); + values.put(Imps.AccountStatus.CONNECTION_STATUS, connectionStatus); cr.insert(uri, values); } @@ -324,20 +324,20 @@ public class ImConnectionAdapter extends IImConnection.Stub { switch (state) { case ImConnection.DISCONNECTED: case ImConnection.LOGGING_OUT: - return Im.ConnectionStatus.OFFLINE; + return Imps.ConnectionStatus.OFFLINE; case ImConnection.LOGGING_IN: - return Im.ConnectionStatus.CONNECTING; + return Imps.ConnectionStatus.CONNECTING; case ImConnection.LOGGED_IN: - return Im.ConnectionStatus.ONLINE; + return Imps.ConnectionStatus.ONLINE; case ImConnection.SUSPENDED: case ImConnection.SUSPENDING: - return Im.ConnectionStatus.SUSPENDED; + return Imps.ConnectionStatus.SUSPENDED; default: - return Im.ConnectionStatus.OFFLINE; + return Imps.ConnectionStatus.OFFLINE; } } @@ -449,15 +449,15 @@ public class ImConnectionAdapter extends IImConnection.Stub { public void onGroupInvitation(Invitation invitation) { String sender = invitation.getSender().getScreenName(); ContentValues values = new ContentValues(7); - values.put(Im.Invitation.PROVIDER, mProviderId); - values.put(Im.Invitation.ACCOUNT, mAccountId); - values.put(Im.Invitation.INVITE_ID, invitation.getInviteID()); - values.put(Im.Invitation.SENDER, sender); - values.put(Im.Invitation.GROUP_NAME, invitation.getGroupAddress().getScreenName()); - values.put(Im.Invitation.NOTE, invitation.getReason()); - values.put(Im.Invitation.STATUS, Im.Invitation.STATUS_PENDING); + values.put(Imps.Invitation.PROVIDER, mProviderId); + values.put(Imps.Invitation.ACCOUNT, mAccountId); + values.put(Imps.Invitation.INVITE_ID, invitation.getInviteID()); + values.put(Imps.Invitation.SENDER, sender); + values.put(Imps.Invitation.GROUP_NAME, invitation.getGroupAddress().getScreenName()); + values.put(Imps.Invitation.NOTE, invitation.getReason()); + values.put(Imps.Invitation.STATUS, Imps.Invitation.STATUS_PENDING); ContentResolver resolver = mService.getContentResolver(); - Uri uri = resolver.insert(Im.Invitation.CONTENT_URI, values); + Uri uri = resolver.insert(Imps.Invitation.CONTENT_URI, values); long id = ContentUris.parseId(uri); try { if (mRemoteListener != null) { diff --git a/src/com/android/im/service/RemoteImService.java b/src/com/android/im/service/RemoteImService.java index cb350bd..5764f1e 100644 --- a/src/com/android/im/service/RemoteImService.java +++ b/src/com/android/im/service/RemoteImService.java @@ -28,14 +28,9 @@ import java.util.Vector; import android.app.Service; import android.content.BroadcastReceiver; import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkConnectivityListener; @@ -49,34 +44,32 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.SystemProperties; -import android.provider.Im; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; -import com.android.im.app.DatabaseUtils; import com.android.im.IConnectionCreationListener; import com.android.im.IImConnection; import com.android.im.IRemoteImService; +import com.android.im.app.ImPluginHelper; import com.android.im.engine.ConnectionFactory; import com.android.im.engine.ImConnection; import com.android.im.engine.ImException; import com.android.im.imps.ImpsConnectionConfig; -import com.android.im.plugin.IImPlugin; import com.android.im.plugin.ImConfigNames; -import com.android.im.plugin.ImPluginConstants; import com.android.im.plugin.ImPluginInfo; import com.android.im.plugin.ImpsConfigNames; -import dalvik.system.PathClassLoader; +import com.android.im.provider.Imps; + public class RemoteImService extends Service { private static final String[] ACCOUNT_PROJECTION = { - Im.Account._ID, - Im.Account.PROVIDER, - Im.Account.USERNAME, - Im.Account.PASSWORD, + Imps.Account._ID, + Imps.Account.PROVIDER, + Imps.Account.USERNAME, + Imps.Account.PASSWORD, }; private static final int ACCOUNT_ID_COLUMN = 0; private static final int ACCOUNT_PROVIDER_COLUMN = 1; @@ -98,15 +91,14 @@ public class RemoteImService extends Service { private SettingsMonitor mSettingsMonitor; + private ImPluginHelper mPluginHelper; Vector mConnections; final RemoteCallbackList mRemoteListeners = new RemoteCallbackList(); - private HashMap mPlugins; public RemoteImService() { mConnections = new Vector(); - mPlugins = new HashMap(); } @Override @@ -128,89 +120,11 @@ public class RemoteImService extends Service { = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); setBackgroundData(manager.getBackgroundDataSetting()); - findAvaiablePlugins(); + mPluginHelper = ImPluginHelper.getInstance(this); + mPluginHelper.loadAvaiablePlugins(); AndroidSystemService.getInstance().initialize(this); } - private void findAvaiablePlugins() { - PackageManager pm = getPackageManager(); - List plugins = pm.queryIntentServices( - new Intent(ImPluginConstants.PLUGIN_ACTION_NAME), PackageManager.GET_META_DATA); - for (ResolveInfo info : plugins) { - Log.d(TAG, "Found plugin " + info); - - ServiceInfo serviceInfo = info.serviceInfo; - if (serviceInfo == null) { - Log.e(TAG, "Ignore bad IM plugin: " + info); - continue; - } - String providerName = null; - String providerFullName = null; - String signUpUrl = null; - Bundle metaData = serviceInfo.metaData; - if (metaData != null) { - providerName = metaData.getString(ImPluginConstants.METADATA_PROVIDER_NAME); - providerFullName = metaData.getString(ImPluginConstants.METADATA_PROVIDER_FULL_NAME); - signUpUrl = metaData.getString(ImPluginConstants.METADATA_SIGN_UP_URL); - } - if (TextUtils.isEmpty(providerName) || TextUtils.isEmpty(providerFullName)) { - Log.e(TAG, "Ignore bad IM plugin: " + info + ". Lack of required meta data"); - continue; - } - - ImPluginInfo pluginInfo = new ImPluginInfo(providerName, serviceInfo.packageName, - serviceInfo.name, serviceInfo.applicationInfo.sourceDir); - - Map config = loadProviderConfigFromPlugin(pluginInfo); - if (config == null) { - Log.e(TAG, "Ignore bad IM plugin"); - break; - } - - config.put(ImConfigNames.PLUGIN_PATH, pluginInfo.mSrcPath); - config.put(ImConfigNames.PLUGIN_CLASS, pluginInfo.mClassName); - long providerId = DatabaseUtils.updateProviderDb(getContentResolver(), - providerName, providerFullName, signUpUrl, config); - mPlugins.put(providerId, pluginInfo); - } - } - - private Map loadProviderConfigFromPlugin(ImPluginInfo pluginInfo) { - // XXX Load the plug-in implementation directly from the apk rather than - // binding to the service and call through IPC Binder API. This is much - // more effective since we don't need to start the service in other - // process. We can not run the plug-in service in the same process as a - // local service because that the interface is defined in a shared - // library in order to compile the plug-in separately. In this case, the - // interface will be loaded by two class loader separately and a - // ClassCastException will be thrown if we cast the binder to the - // interface. - PathClassLoader loader = new PathClassLoader(pluginInfo.mSrcPath, getClassLoader()); - try { - Class cls = loader.loadClass(pluginInfo.mClassName); - Method m = cls.getMethod("onBind", Intent.class); - IImPlugin plugin = (IImPlugin)m.invoke(cls.newInstance(), new Object[]{null}); - return plugin.getProviderConfig(); - } catch (ClassNotFoundException e) { - Log.e(TAG, "Could not find plugin class", e); - } catch (IllegalAccessException e) { - Log.e(TAG, "Could not create plugin instance", e); - } catch (InstantiationException e) { - Log.e(TAG, "Could not create plugin instance", e); - } catch (SecurityException e) { - Log.e(TAG, "Could not load config from the plugin", e); - } catch (NoSuchMethodException e) { - Log.e(TAG, "Could not load config from the plugin", e); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Could not load config from the plugin", e); - } catch (InvocationTargetException e) { - Log.e(TAG, "Could not load config from the plugin", e); - } catch (RemoteException e) { - Log.e(TAG, "Could not load config from the plugin", e); - } - return null; - } - @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); @@ -232,8 +146,8 @@ public class RemoteImService extends Service { ContentResolver resolver = getContentResolver(); - String where = Im.Account.KEEP_SIGNED_IN + "=1 AND " + Im.Account.ACTIVE + "=1"; - Cursor cursor = resolver.query(Im.Account.CONTENT_URI, + String where = Imps.Account.KEEP_SIGNED_IN + "=1 AND " + Imps.Account.ACTIVE + "=1"; + Cursor cursor = resolver.query(Imps.Account.CONTENT_URI, ACCOUNT_PROJECTION, where, null, null); if (cursor == null) { Log.w(TAG, "Can't query account!"); @@ -258,7 +172,7 @@ public class RemoteImService extends Service { private Map loadProviderSettings(long providerId) { ContentResolver cr = getContentResolver(); - Map settings = Im.ProviderSettings.queryProviderSettings(cr, providerId); + Map settings = Imps.ProviderSettings.queryProviderSettings(cr, providerId); NetworkInfo networkInfo = mNetworkConnectivityListener.getNetworkInfo(); // Insert a fake msisdn on emulator. We don't need this on device @@ -454,8 +368,8 @@ public class RemoteImService extends Service { private final IRemoteImService.Stub mBinder = new IRemoteImService.Stub() { - public List getAllPlugins() { - return new ArrayList(mPlugins.values()); + public List getAllPlugins() { + return new ArrayList(mPluginHelper.getPluginsInfo()); } public void addConnectionCreatedListener(IConnectionCreationListener listener) { diff --git a/src/com/android/im/service/StatusBarNotifier.java b/src/com/android/im/service/StatusBarNotifier.java index fb3d1ba..071969e 100644 --- a/src/com/android/im/service/StatusBarNotifier.java +++ b/src/com/android/im/service/StatusBarNotifier.java @@ -28,13 +28,13 @@ import android.content.Intent; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; -import android.provider.Im; import android.text.TextUtils; import android.util.Log; import com.android.im.R; import com.android.im.app.ContactListActivity; import com.android.im.app.NewChatActivity; +import com.android.im.provider.Imps; public class StatusBarNotifier { private static final boolean DBG = false; @@ -46,7 +46,7 @@ public class StatusBarNotifier { private Context mContext; private NotificationManager mNotificationManager; - private HashMap mSettings; + private HashMap mSettings; private Handler mHandler; private HashMap mNotificationInfos; private long mLastSoundPlayedMs; @@ -55,13 +55,13 @@ public class StatusBarNotifier { mContext = context; mNotificationManager = (NotificationManager) context.getSystemService( Context.NOTIFICATION_SERVICE); - mSettings = new HashMap(); + mSettings = new HashMap(); mHandler = new Handler(); mNotificationInfos = new HashMap(); } public void onServiceStop() { - for(Im.ProviderSettings.QueryMap queryMap : mSettings.values()) { + for(Imps.ProviderSettings.QueryMap queryMap : mSettings.values()) { queryMap.close(); } } @@ -76,7 +76,7 @@ public class StatusBarNotifier { String title = nickname; String snippet = nickname + ": " + msg; Intent intent = new Intent(Intent.ACTION_VIEW, - ContentUris.withAppendedId(Im.Chats.CONTENT_URI, chatId)); + ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, chatId)); intent.addCategory(com.android.im.app.ImApp.IMPS_CATEGORY); notify(username, title, snippet, msg, providerId, accountId, intent, lightWeightNotify); } @@ -90,7 +90,7 @@ public class StatusBarNotifier { String title = nickname; String message = mContext.getString(R.string.subscription_notify_text, nickname); Intent intent = new Intent(ImServiceConstants.ACTION_MANAGE_SUBSCRIPTION, - ContentUris.withAppendedId(Im.Contacts.CONTENT_URI, contactId)); + ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, contactId)); intent.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, providerId); intent.putExtra(ImServiceConstants.EXTRA_INTENT_FROM_ADDRESS, username); notify(username, title, message, message, providerId, accountId, intent, false); @@ -100,7 +100,7 @@ public class StatusBarNotifier { long invitationId, String username) { Intent intent = new Intent(Intent.ACTION_VIEW, - ContentUris.withAppendedId(Im.Invitation.CONTENT_URI, invitationId)); + ContentUris.withAppendedId(Imps.Invitation.CONTENT_URI, invitationId)); String title = mContext.getString(R.string.notify_groupchat_label); String message = mContext.getString( @@ -146,10 +146,10 @@ public class StatusBarNotifier { } } - private Im.ProviderSettings.QueryMap getProviderSettings(long providerId) { - Im.ProviderSettings.QueryMap res = mSettings.get(providerId); + private Imps.ProviderSettings.QueryMap getProviderSettings(long providerId) { + Imps.ProviderSettings.QueryMap res = mSettings.get(providerId); if (res == null) { - res = new Im.ProviderSettings.QueryMap(mContext.getContentResolver(), + res = new Imps.ProviderSettings.QueryMap(mContext.getContentResolver(), providerId, true, mHandler); mSettings.put(providerId, res); } @@ -157,7 +157,7 @@ public class StatusBarNotifier { } private boolean isNotificationEnabled(long providerId) { - Im.ProviderSettings.QueryMap settings = getProviderSettings(providerId); + Imps.ProviderSettings.QueryMap settings = getProviderSettings(providerId); return settings.getEnableNotification(); } @@ -178,7 +178,7 @@ public class StatusBarNotifier { } private void setRinger(long providerId, Notification notification) { - Im.ProviderSettings.QueryMap settings = getProviderSettings(providerId); + Imps.ProviderSettings.QueryMap settings = getProviderSettings(providerId); String ringtoneUri = settings.getRingtoneURI(); boolean vibrate = settings.getVibrate(); @@ -262,7 +262,7 @@ public class StatusBarNotifier { private Intent getDefaultIntent() { Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setType(Im.Contacts.CONTENT_TYPE); + intent.setType(Imps.Contacts.CONTENT_TYPE); intent.setClass(mContext, ContactListActivity.class); intent.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); @@ -287,7 +287,7 @@ public class StatusBarNotifier { return item.mTitle; } else { return mContext.getString(R.string.newMessages_label, - Im.Provider.getProviderNameForId(mContext.getContentResolver(), mProviderId)); + Imps.Provider.getProviderNameForId(mContext.getContentResolver(), mProviderId)); } } -- cgit v1.2.3 From ea5efba86520eebc2944998f27f69df5ca8d3fb8 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 12 Jan 2010 15:19:14 -0800 Subject: android-2.1_r1 snapshot --- res/values-cs/strings.xml | 18 +++++++++--------- res/values-da/strings.xml | 42 +++++++++++++++++++++--------------------- res/values-de/strings.xml | 6 +++--- res/values-es/strings.xml | 4 ++-- res/values-ko/strings.xml | 6 +++--- res/values-pl/strings.xml | 6 +++--- res/values-ru/strings.xml | 12 ++++++------ res/values-sv/strings.xml | 4 ++-- res/values-zh-rCN/strings.xml | 10 +++++----- 9 files changed, 54 insertions(+), 54 deletions(-) diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index 7d5e716..2586987 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -94,10 +94,10 @@ "Chat s uživatelem %1$s" "Já" "Sem zadejte zprávu" - "uživatel %1$s je online" + "%1$s je online" "uživatel %1$s je pryč" "%1$s je zaneprázdněn" - "uživatel %1$s je offline" + "%1$s je offline" "Uživatel %1$s se připojil" "uživatel %1$s odešel" "'Odesláno v 'hh':'mm' 'a' dne 'EEEE" @@ -176,21 +176,21 @@ "Kód chyby: %1$d" "Happy" - "Smutný" - "Mrkající" + "Ach jo" + "Mrknutí" "Vypláznutý jazyk" "Překvapený" "Pusa" - "Hej!" + "Zlobím se!" "Cool" "Cinkání zlaťáků" - "Šlápota v úsměvu" + "Trapas!" "V rozpacích" "Andílek" "Nerozhodný" - "Rozplakaný" + "Bůůů, fňuk, fňuk" "Ani muk" - "Smějící se" - "Zmatený" + "Smích" + "Zmatek"
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index 07661dc..5e0931a 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -16,9 +16,9 @@ "læs chatbeskeder" - "Tillader programmer at læse data fra IM-indholdsleverandøren." + "Tillader, at programmer læser data fra IM-indholdsleverandøren." "skriv chatbeskeder" - "Tillader programmer at skrive data til IM-indholdsleverandøren." + "Tillader, at programmer skriver data til IM-indholdsleverandøren." "IM" "Chat – Vælg en konto" "Tilføj konto" @@ -40,7 +40,7 @@ "Afslut chat" "Liste over kontakter" "Inviter ..." - "Skift chats" + "Skift chatsamtale" "Indsæt smiley" "Menu+" "Input" @@ -61,7 +61,7 @@ "Husk min adgangskode." "Log mig automatisk ind." "Har du ikke en konto?" - "Denne valgmulighed logger dig automatisk ind, hver gang du åbner dette program. For at deaktivere denne valgmulighed skal du logge ud og så fjerne markeringen i afkrydsningsfeltet \"Log mig ind automatisk\"." + "Denne indstilling logger dig automatisk ind, hver gang du åbner dette program. For at deaktivere denne indstilling skal du logge ud og derefter fjerne markeringen i afkrydsningsfeltet \"Log mig ind automatisk\"." "Log ind" "Logger ind på %1$s" "Logger ind ..." @@ -69,7 +69,7 @@ "%1$s kræver, at baggrundsdata aktiveres." "Aktiver" "Afslut" - "Igangværende chats (%1$d)" + "Igangværende chatsamtaler (%1$d)" "%1$d er online" "Venneinvitationer" "(""Ukendt"")" @@ -93,12 +93,12 @@ "Vis som offline" "Chat med %1$s" "Mig" - "Indtast for at skrive" + "Indtast tekst" "%1$s er online" "%1$s er ikke til stede" "%1$s er optaget" "%1$s er offline" - "%1$s har tilsluttet sig" + "%1$s deltager nu" "%1$s er gået" "'Sendt klokken 'hh':'mm' 'a' den 'EEEE" "Send" @@ -107,21 +107,21 @@ "%1$s er offline. Dine sendte beskeder sendes, når %1$s er online igen." "%1$s er ikke på din liste over kontakter." "Vælg link" - "Der er ingen aktive chats." + "Der er ingen aktive chatsamtaler." "Tilføj kontakt" "E-mail-adresse på den person, du ønsker at invitere:" "Vælg en liste:" "Indtast et navn for at tilføje fra Kontakter." "Send invitation" "Generelle indstillinger" - "Skjul offline kontakter" - "Meddelelsesindstillinger" + "Skjul kontakter, der er offline" + "Indstillinger for meddelelser" "IM-meddelelser" - "Underret i statuslinjen, når der kommer en IM" - "Vibrer" - "Vibrer også, når der kommer en IM" + "Vis i statuslinjen, når der kommer en IM" + "Vibration" + "Vibration ved ny IM" "Lyd" - "Spil også ringetone, når der kommer en IM" + "Afspil ringetone, når der kommer en IM" "Vælg ringetone" "%1$s har inviteret dig til at deltage i en gruppechat." "Invitationen er sendt til %1$s." @@ -131,18 +131,18 @@ "Accepter" "Afvis" "Tilmelding fra %1$s kunne ikke godkendes. Prøv igen senere." - "Tilmelding fra %1$s kunne ikke afvises. Prøv igen senere." + "Tilmeldingen fra %1$s kunne ikke afvises. Prøv igen senere." "Nye %1$s-beskeder" - "%1$d ulæste chats" + "%1$d ulæste chatbeskeder" "Ny venneinvitation fra %s" - "Gruppechatinvitation" - "Ny gruppechatinvitation fra %s" + "Invitation til gruppechat" + "Ny invitation til gruppechat fra %s" "Kontakten \"%1$s\" er tilføjet." "Kontakten \"%1$s\" er slettet." "Kontakten \"%1$s\" er blokeret." "Kontakten \"%1$s\" er ikke længere blokeret." "start IM-tjeneste" - "Lader programmer starte IM-tjeneste efter hensigt." + "Tillader, at programmer starter IM-tjenesten efter hensigt." "Bemærk" "Der kunne ikke logges ind på tjenesten %1$s. Prøv igen senere."\n"(Detalje: %2$s)" "Listen blev ikke tilføjet." @@ -150,7 +150,7 @@ "Blokeringen af kontakten blev ikke ophævet." "Vælg en kontakt først." "Afbrudt!"\n - "Servicefejl!" + "Tjenestefejl!" "Listen over kontakter blev ikke indlæst." "Der kan ikke oprettes forbindelse til serveren. Kontroller din forbindelse." "%1$s findes allerede på din liste over kontakter." @@ -167,7 +167,7 @@ "Beskedkøen er fuld." "Serveren understøtter ikke videresendelse til domænet." "Det indtastede brugernavn kunne ikke genkendes." - "Beklager! Du er blokeret af brugeren." + "Beklager! Brugeren har blokeret dig." "Sessionen er udløbet. Log ind igen." "du er logget ind fra en anden klient." "du er allerede logget ind fra en anden klient." diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 749cb3a..0ab9bca 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -56,7 +56,7 @@ "Sie wurden von %1$s abgemeldet." "Sie wurden aus folgendem Grund von %1$s abgemeldet: %2$s." "%1$s-Konto hinzufügen" - "Benutzername:" + "Nutzername:" "Kennwort:" "Kennwort speichern" "Automatisch anmelden" @@ -166,8 +166,8 @@ "Die aktuelle Version wird vom Server nicht unterstützt." "Die Nachrichtenwarteschlange ist voll." "Das Weiterleiten an die Domäne wird vom Server nicht unterstützt." - "Der eingegebene Benutzername wird nicht erkannt." - "Sie werden leider vom Benutzer gesperrt." + "Der eingegebene Nutzername wird nicht erkannt." + "Sie werden leider vom Nutzer gesperrt." "Die Sitzung ist abgelaufen, bitte melden Sie sich erneut an." "Sie haben sich von einem anderen Client aus angemeldet." "Sie haben sich bereits von einem anderen Client aus angemeldet." diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 98c2e2e..4d5d0e8 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -97,14 +97,14 @@ "%1$s está conectado" "%1$s está ausente" "%1$s está ocupado" - "%1$s está desconectado" + "%1$s está desconectado/a" "%1$s se ha unido al chat" "%1$s se ha desconectado" "'Enviado a las 'hh':'mm' 'a' el 'EEEE" "Enviar" "No se ha podido enviar el mensaje." "Se ha perdido la conexión con el servidor. Los mensajes se enviarán cuando se restablezca la conexión." - "%1$s está desconectado. Los mensajes enviados se entregarán cuando %1$s vuelva a conectarse." + "%1$s está desconectado/a. Los mensajes enviados se entregarán cuando %1$s vuelva a conectarse." "%1$s no figura en tu lista de contactos." "Seleccionar enlace" "No hay ningún chat activo." diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index cf98293..84add88 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -117,11 +117,11 @@ "오프라인 주소록 숨기기" "알림 설정" "메신저 알림" - "메신저가 오면 상태 표시줄에 알림" + "메시지가 오면 상태 표시줄에 알림" "진동" - "메신저가 오면 진동" + "메시지가 오면 진동" "사운드" - "메신저가 오면 벨소리 재생" + "메시지가 오면 벨소리 재생" "벨소리 선택" "%1$s님이 그룹 채팅에 초대하셨습니다." "%1$s님에게 초대장을 보냈습니다." diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 0da89a3..be70cc8 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -62,7 +62,7 @@ "Zaloguj mnie automatycznie." "Nie masz konta?" "Ta opcja powoduje automatyczne logowanie użytkownika przy każdym uruchomieniu tej aplikacji. Aby wyłączyć tę opcję, wyloguj się, a następnie usuń zaznaczenie pola wyboru „Zaloguj automatycznie”." - "Zaloguj" + "Zaloguj się" "Logowanie do konta %1$s" "Trwa logowanie…" "Dane w tle są wyłączone" @@ -185,12 +185,12 @@ "Na luzie" "Pazerny" "Nietaktowny" - "Zakłopotanie" + "Zawstydzony" "Anioł" "Niezdecydowany" "Płacze" "Milczy jak grób" "Śmieje się" - "Zakłopotanie" + "Zakłopotany"
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index c243a82..d0883ed 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -65,8 +65,8 @@ "Вход" "Вход в %1$s" "Выполняется вход..." - "Исходные данные отключены" - "%1$s требует, чтобы использование фоновых данных было разрешено" + "Фоновый режим обмена данными отключен" + "Для корректной работы в сети %1$s требуется включенный режим фонового обмена данными." "Включить" "Выйти" "Текущих чатов: (%1$d)" @@ -117,12 +117,12 @@ "Скрыть контакты, отсутствующие в сети" "Настройки уведомлений" "Уведомления чата" - "Уведомлять о получении сообщения чата в строке состояния" - "Вибрация" - "Сигнал вибрации при получении сообщения чата" + "Показывать уведомления в строке состояния при получении новых соообщений" + "Вибровызов" + "Вибросигнал при получении сообщения чата" "Звуковой сигнал" "Воспроизводить мелодию звонка при получении сообщения чата" - "Выбрать мелодию звонка" + "Мелодия звонка" "Пользователь %1$s приглашает вас в групповой чат." "Приглашение отправлено пользователю %1$s." "Принять" diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index ed23f4e..c893b22 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -31,7 +31,7 @@ "Lägg till kontakt" "Ta bort kontakt" "Blockera kontakt" - "Blockerad" + "Blockerade" "Kontolista" "Inställningar" "Öppna chatt" @@ -42,7 +42,7 @@ "Bjud in…" "Byt chatt" "Infoga känsloikon" - "Meny+" + "Menu+" "Indata" "Bekräfta" "Vill du logga ut från alla tjänster?" diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index ef7ae12..b095d80 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -41,7 +41,7 @@ "联系人列表" "邀请..." "切换聊天" - "插入表情符" + "插入表情符号" "MENU+" "输入" "确认" @@ -175,13 +175,13 @@ "您当前未登录。" "错误代码 %1$d" - "幸福" - "悲伤" + "开心" + "忧伤" "眨眼" - "吐舌头" + "吐舌" "惊讶" "亲吻" - "大喊" + "叫喊" "酷" "财迷" "说错了话" -- cgit v1.2.3