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