diff options
Diffstat (limited to 'linden/indra/newview/llimview.cpp')
-rw-r--r-- | linden/indra/newview/llimview.cpp | 294 |
1 files changed, 255 insertions, 39 deletions
diff --git a/linden/indra/newview/llimview.cpp b/linden/indra/newview/llimview.cpp index a4fd729..38471ad 100644 --- a/linden/indra/newview/llimview.cpp +++ b/linden/indra/newview/llimview.cpp | |||
@@ -4,6 +4,7 @@ | |||
4 | * | 4 | * |
5 | * Copyright (c) 2001-2007, Linden Research, Inc. | 5 | * Copyright (c) 2001-2007, Linden Research, Inc. |
6 | * | 6 | * |
7 | * Second Life Viewer Source Code | ||
7 | * The source code in this file ("Source Code") is provided by Linden Lab | 8 | * The source code in this file ("Source Code") is provided by Linden Lab |
8 | * to you under the terms of the GNU General Public License, version 2.0 | 9 | * to you under the terms of the GNU General Public License, version 2.0 |
9 | * ("GPL"), unless you have obtained a separate licensing agreement | 10 | * ("GPL"), unless you have obtained a separate licensing agreement |
@@ -42,6 +43,7 @@ | |||
42 | #include "llviewerwindow.h" | 43 | #include "llviewerwindow.h" |
43 | #include "llresmgr.h" | 44 | #include "llresmgr.h" |
44 | #include "llfloaternewim.h" | 45 | #include "llfloaternewim.h" |
46 | #include "llhttpnode.h" | ||
45 | #include "llimpanel.h" | 47 | #include "llimpanel.h" |
46 | #include "llresizebar.h" | 48 | #include "llresizebar.h" |
47 | #include "lltabcontainer.h" | 49 | #include "lltabcontainer.h" |
@@ -54,7 +56,6 @@ | |||
54 | #include "llcallingcard.h" | 56 | #include "llcallingcard.h" |
55 | #include "lltoolbar.h" | 57 | #include "lltoolbar.h" |
56 | 58 | ||
57 | const EInstantMessage EVERYONE_DIALOG = IM_NOTHING_SPECIAL; | ||
58 | const EInstantMessage GROUP_DIALOG = IM_SESSION_GROUP_START; | 59 | const EInstantMessage GROUP_DIALOG = IM_SESSION_GROUP_START; |
59 | const EInstantMessage DEFAULT_DIALOG = IM_NOTHING_SPECIAL; | 60 | const EInstantMessage DEFAULT_DIALOG = IM_NOTHING_SPECIAL; |
60 | 61 | ||
@@ -69,6 +70,9 @@ LLIMView* gIMView = NULL; | |||
69 | static LLString sOnlyUserMessage; | 70 | static LLString sOnlyUserMessage; |
70 | static LLString sOfflineMessage; | 71 | static LLString sOfflineMessage; |
71 | 72 | ||
73 | static std::map<std::string,LLString> sEventStringsMap; | ||
74 | static std::map<std::string,LLString> sErrorStringsMap; | ||
75 | static std::map<std::string,LLString> sForceCloseSessionMap; | ||
72 | // | 76 | // |
73 | // Helper Functions | 77 | // Helper Functions |
74 | // | 78 | // |
@@ -82,7 +86,8 @@ static BOOL group_dictionary_sort( LLGroupData* a, LLGroupData* b ) | |||
82 | 86 | ||
83 | // the other_participant_id is either an agent_id, a group_id, or an inventory | 87 | // the other_participant_id is either an agent_id, a group_id, or an inventory |
84 | // folder item_id (collection of calling cards) | 88 | // folder item_id (collection of calling cards) |
85 | static LLUUID compute_session_id(EInstantMessage dialog, const LLUUID& other_participant_id) | 89 | static LLUUID compute_session_id(EInstantMessage dialog, |
90 | const LLUUID& other_participant_id) | ||
86 | { | 91 | { |
87 | LLUUID session_id; | 92 | LLUUID session_id; |
88 | if (IM_SESSION_GROUP_START == dialog) | 93 | if (IM_SESSION_GROUP_START == dialog) |
@@ -90,6 +95,10 @@ static LLUUID compute_session_id(EInstantMessage dialog, const LLUUID& other_par | |||
90 | // slam group session_id to the group_id (other_participant_id) | 95 | // slam group session_id to the group_id (other_participant_id) |
91 | session_id = other_participant_id; | 96 | session_id = other_participant_id; |
92 | } | 97 | } |
98 | else if (IM_SESSION_CONFERENCE_START == dialog) | ||
99 | { | ||
100 | session_id.generate(); | ||
101 | } | ||
93 | else | 102 | else |
94 | { | 103 | { |
95 | LLUUID agent_id = gAgent.getID(); | 104 | LLUUID agent_id = gAgent.getID(); |
@@ -121,11 +130,34 @@ BOOL LLFloaterIM::postBuild() | |||
121 | { | 130 | { |
122 | requires("only_user_message", WIDGET_TYPE_TEXT_BOX); | 131 | requires("only_user_message", WIDGET_TYPE_TEXT_BOX); |
123 | requires("offline_message", WIDGET_TYPE_TEXT_BOX); | 132 | requires("offline_message", WIDGET_TYPE_TEXT_BOX); |
133 | requires("generic_request_error", WIDGET_TYPE_TEXT_BOX); | ||
134 | requires("insufficient_perms_error", WIDGET_TYPE_TEXT_BOX); | ||
135 | requires("generic_request_error", WIDGET_TYPE_TEXT_BOX); | ||
136 | requires("add_session_event", WIDGET_TYPE_TEXT_BOX); | ||
137 | requires("message_session_event", WIDGET_TYPE_TEXT_BOX); | ||
138 | requires("removed_from_group", WIDGET_TYPE_TEXT_BOX); | ||
124 | 139 | ||
125 | if (checkRequirements()) | 140 | if (checkRequirements()) |
126 | { | 141 | { |
127 | sOnlyUserMessage = childGetText("only_user_message"); | 142 | sOnlyUserMessage = childGetText("only_user_message"); |
128 | sOfflineMessage = childGetText("offline_message"); | 143 | sOfflineMessage = childGetText("offline_message"); |
144 | |||
145 | sErrorStringsMap["generic"] = | ||
146 | childGetText("generic_request_error"); | ||
147 | sErrorStringsMap["unverified"] = | ||
148 | childGetText("insufficient_perms_error"); | ||
149 | sErrorStringsMap["no_user_911"] = | ||
150 | childGetText("user_no_help"); | ||
151 | |||
152 | sEventStringsMap["add"] = childGetText("add_session_event");; | ||
153 | sEventStringsMap["message"] = | ||
154 | childGetText("message_session_event");; | ||
155 | sEventStringsMap["teleport"] = | ||
156 | childGetText("teleport_session_event");; | ||
157 | |||
158 | sForceCloseSessionMap["removed"] = | ||
159 | childGetText("removed_from_group"); | ||
160 | |||
129 | return TRUE; | 161 | return TRUE; |
130 | } | 162 | } |
131 | return FALSE; | 163 | return FALSE; |
@@ -209,16 +241,12 @@ protected: | |||
209 | // static | 241 | // static |
210 | EInstantMessage LLIMView::defaultIMTypeForAgent(const LLUUID& agent_id) | 242 | EInstantMessage LLIMView::defaultIMTypeForAgent(const LLUUID& agent_id) |
211 | { | 243 | { |
212 | EInstantMessage type = IM_SESSION_CARDLESS_START; | 244 | EInstantMessage type = IM_NOTHING_SPECIAL; |
213 | if(is_agent_friend(agent_id)) | 245 | if(is_agent_friend(agent_id)) |
214 | { | 246 | { |
215 | if(LLAvatarTracker::instance().isBuddyOnline(agent_id)) | 247 | if(LLAvatarTracker::instance().isBuddyOnline(agent_id)) |
216 | { | 248 | { |
217 | type = IM_SESSION_ADD; | 249 | type = IM_SESSION_CONFERENCE_START; |
218 | } | ||
219 | else | ||
220 | { | ||
221 | type = IM_SESSION_OFFLINE_ADD; | ||
222 | } | 250 | } |
223 | } | 251 | } |
224 | return type; | 252 | return type; |
@@ -426,18 +454,25 @@ BOOL LLIMView::isIMSessionOpen(const LLUUID& uuid) | |||
426 | // exists, it is brought forward. Specifying id = NULL results in an | 454 | // exists, it is brought forward. Specifying id = NULL results in an |
427 | // im session to everyone. Returns the uuid of the session. | 455 | // im session to everyone. Returns the uuid of the session. |
428 | LLUUID LLIMView::addSession(const std::string& name, | 456 | LLUUID LLIMView::addSession(const std::string& name, |
429 | EInstantMessage dialog, | 457 | EInstantMessage dialog, |
430 | const LLUUID& other_participant_id) | 458 | const LLUUID& other_participant_id) |
431 | { | 459 | { |
432 | LLUUID session_id = compute_session_id(dialog, other_participant_id); | 460 | LLUUID session_id = compute_session_id(dialog, other_participant_id); |
461 | |||
433 | LLFloaterIMPanel* floater = findFloaterBySession(session_id); | 462 | LLFloaterIMPanel* floater = findFloaterBySession(session_id); |
434 | if(!floater) | 463 | if(!floater) |
435 | { | 464 | { |
436 | floater = createFloater(session_id, other_participant_id, name, dialog, TRUE); | ||
437 | LLDynamicArray<LLUUID> ids; | 465 | LLDynamicArray<LLUUID> ids; |
438 | ids.put(other_participant_id); | 466 | ids.put(other_participant_id); |
467 | |||
468 | floater = createFloater(session_id, | ||
469 | other_participant_id, | ||
470 | name, | ||
471 | ids, | ||
472 | dialog, | ||
473 | TRUE); | ||
474 | |||
439 | noteOfflineUsers(floater, ids); | 475 | noteOfflineUsers(floater, ids); |
440 | floater->addParticipants(ids); | ||
441 | mTalkFloater->showFloater(floater); | 476 | mTalkFloater->showFloater(floater); |
442 | } | 477 | } |
443 | else | 478 | else |
@@ -452,30 +487,30 @@ LLUUID LLIMView::addSession(const std::string& name, | |||
452 | // Adds a session using the given session_id. If the session already exists | 487 | // Adds a session using the given session_id. If the session already exists |
453 | // the dialog type is assumed correct. Returns the uuid of the session. | 488 | // the dialog type is assumed correct. Returns the uuid of the session. |
454 | LLUUID LLIMView::addSession(const std::string& name, | 489 | LLUUID LLIMView::addSession(const std::string& name, |
455 | EInstantMessage dialog, | 490 | EInstantMessage dialog, |
456 | const LLUUID& session_id, | 491 | const LLUUID& other_participant_id, |
457 | const LLDynamicArray<LLUUID>& ids) | 492 | const LLDynamicArray<LLUUID>& ids) |
458 | { | 493 | { |
459 | if (0 == ids.getLength()) | 494 | if (0 == ids.getLength()) |
460 | { | 495 | { |
461 | return LLUUID::null; | 496 | return LLUUID::null; |
462 | } | 497 | } |
463 | 498 | ||
464 | LLUUID other_participant_id = ids[0]; | 499 | LLUUID session_id = compute_session_id(dialog, |
465 | LLUUID new_session_id = session_id; | 500 | other_participant_id); |
466 | if (new_session_id.isNull()) | ||
467 | { | ||
468 | new_session_id = compute_session_id(dialog, other_participant_id); | ||
469 | } | ||
470 | 501 | ||
471 | LLFloaterIMPanel* floater = findFloaterBySession(new_session_id); | 502 | LLFloaterIMPanel* floater = findFloaterBySession(session_id); |
472 | if(!floater) | 503 | if(!floater) |
473 | { | 504 | { |
474 | // On creation, use the first element of ids as the "other_participant_id" | 505 | // On creation, use the first element of ids as the "other_participant_id" |
475 | floater = createFloater(new_session_id, other_participant_id, name, dialog, TRUE); | 506 | floater = createFloater(session_id, |
507 | other_participant_id, | ||
508 | name, | ||
509 | ids, | ||
510 | dialog, | ||
511 | TRUE); | ||
476 | noteOfflineUsers(floater, ids); | 512 | noteOfflineUsers(floater, ids); |
477 | } | 513 | } |
478 | floater->addParticipants(ids); | ||
479 | mTalkFloater->showFloater(floater); | 514 | mTalkFloater->showFloater(floater); |
480 | //mTabContainer->selectTabPanel(panel); | 515 | //mTabContainer->selectTabPanel(panel); |
481 | floater->setInputFocus(TRUE); | 516 | floater->setInputFocus(TRUE); |
@@ -518,8 +553,7 @@ void LLIMView::refresh() | |||
518 | group; | 553 | group; |
519 | group = group_list.getNextData()) | 554 | group = group_list.getNextData()) |
520 | { | 555 | { |
521 | mNewIMFloater->addTarget(group->mID, group->mName, | 556 | mNewIMFloater->addGroup(group->mID, (void*)(&GROUP_DIALOG), TRUE, FALSE); |
522 | (void*)(&GROUP_DIALOG), TRUE, FALSE); | ||
523 | } | 557 | } |
524 | 558 | ||
525 | // build a set of buddies in the current buddy list. | 559 | // build a set of buddies in the current buddy list. |
@@ -539,13 +573,6 @@ void LLIMView::refresh() | |||
539 | { | 573 | { |
540 | mNewIMFloater->addAgent((*it).second, (void*)(&DEFAULT_DIALOG), FALSE); | 574 | mNewIMFloater->addAgent((*it).second, (void*)(&DEFAULT_DIALOG), FALSE); |
541 | } | 575 | } |
542 | |||
543 | if(gAgent.isGodlike()) | ||
544 | { | ||
545 | // XUI:translate | ||
546 | mNewIMFloater->addTarget(LLUUID::null, "All Residents, All Grids", | ||
547 | (void*)(&EVERYONE_DIALOG), TRUE, FALSE); | ||
548 | } | ||
549 | 576 | ||
550 | mNewIMFloater->setScrollPos( old_scroll_pos ); | 577 | mNewIMFloater->setScrollPos( old_scroll_pos ); |
551 | } | 578 | } |
@@ -619,12 +646,17 @@ void LLIMView::disconnectAllSessions() | |||
619 | std::set<LLViewHandle>::iterator handle_it; | 646 | std::set<LLViewHandle>::iterator handle_it; |
620 | for(handle_it = mFloaters.begin(); | 647 | for(handle_it = mFloaters.begin(); |
621 | handle_it != mFloaters.end(); | 648 | handle_it != mFloaters.end(); |
622 | ++handle_it) | 649 | ) |
623 | { | 650 | { |
624 | floater = (LLFloaterIMPanel*)LLFloater::getFloaterByHandle(*handle_it); | 651 | floater = (LLFloaterIMPanel*)LLFloater::getFloaterByHandle(*handle_it); |
652 | |||
653 | // MUST do this BEFORE calling floater->onClose() because that may remove the item from the set, causing the subsequent increment to crash. | ||
654 | ++handle_it; | ||
655 | |||
625 | if (floater) | 656 | if (floater) |
626 | { | 657 | { |
627 | floater->setEnabled(FALSE); | 658 | floater->setEnabled(FALSE); |
659 | floater->onClose(TRUE); | ||
628 | } | 660 | } |
629 | } | 661 | } |
630 | } | 662 | } |
@@ -662,11 +694,12 @@ BOOL LLIMView::hasSession(const LLUUID& session_id) | |||
662 | // consistency. Returns the pointer, caller (the class instance since | 694 | // consistency. Returns the pointer, caller (the class instance since |
663 | // it is a private method) is not responsible for deleting the | 695 | // it is a private method) is not responsible for deleting the |
664 | // pointer. Add the floater to this but do not select it. | 696 | // pointer. Add the floater to this but do not select it. |
665 | LLFloaterIMPanel* LLIMView::createFloater(const LLUUID& session_id, | 697 | LLFloaterIMPanel* LLIMView::createFloater( |
666 | const LLUUID& other_participant_id, | 698 | const LLUUID& session_id, |
667 | const std::string& session_label, | 699 | const LLUUID& other_participant_id, |
668 | EInstantMessage dialog, | 700 | const std::string& session_label, |
669 | BOOL user_initiated) | 701 | EInstantMessage dialog, |
702 | BOOL user_initiated) | ||
670 | { | 703 | { |
671 | if (session_id.isNull()) | 704 | if (session_id.isNull()) |
672 | { | 705 | { |
@@ -686,6 +719,33 @@ LLFloaterIMPanel* LLIMView::createFloater(const LLUUID& session_id, | |||
686 | return floater; | 719 | return floater; |
687 | } | 720 | } |
688 | 721 | ||
722 | LLFloaterIMPanel* LLIMView::createFloater( | ||
723 | const LLUUID& session_id, | ||
724 | const LLUUID& other_participant_id, | ||
725 | const std::string& session_label, | ||
726 | const LLDynamicArray<LLUUID>& ids, | ||
727 | EInstantMessage dialog, | ||
728 | BOOL user_initiated) | ||
729 | { | ||
730 | if (session_id.isNull()) | ||
731 | { | ||
732 | llwarns << "Creating LLFloaterIMPanel with null session ID" << llendl; | ||
733 | } | ||
734 | llinfos << "LLIMView::createFloater: from " << other_participant_id | ||
735 | << " in session " << session_id << llendl; | ||
736 | LLFloaterIMPanel* floater = new LLFloaterIMPanel(session_label, | ||
737 | LLRect(), | ||
738 | session_label, | ||
739 | session_id, | ||
740 | other_participant_id, | ||
741 | ids, | ||
742 | dialog); | ||
743 | LLTabContainerCommon::eInsertionPoint i_pt = user_initiated ? LLTabContainerCommon::RIGHT_OF_CURRENT : LLTabContainerCommon::END; | ||
744 | mTalkFloater->addFloater(floater, FALSE, i_pt); | ||
745 | mFloaters.insert(floater->getHandle()); | ||
746 | return floater; | ||
747 | } | ||
748 | |||
689 | void LLIMView::noteOfflineUsers(LLFloaterIMPanel* floater, | 749 | void LLIMView::noteOfflineUsers(LLFloaterIMPanel* floater, |
690 | const LLDynamicArray<LLUUID>& ids) | 750 | const LLDynamicArray<LLUUID>& ids) |
691 | { | 751 | { |
@@ -734,3 +794,159 @@ void LLIMView::processIMTypingCore(const LLIMInfo* im_info, BOOL typing) | |||
734 | floater->processIMTyping(im_info, typing); | 794 | floater->processIMTyping(im_info, typing); |
735 | } | 795 | } |
736 | } | 796 | } |
797 | |||
798 | void LLIMView::updateFloaterSessionID(const LLUUID& old_session_id, | ||
799 | const LLUUID& new_session_id) | ||
800 | { | ||
801 | LLFloaterIMPanel* floater = findFloaterBySession(old_session_id); | ||
802 | if (floater) | ||
803 | { | ||
804 | floater->sessionInitReplyReceived(new_session_id); | ||
805 | } | ||
806 | } | ||
807 | |||
808 | void onConfirmForceCloseError(S32 option, void* data) | ||
809 | { | ||
810 | //only 1 option really | ||
811 | LLFloaterIMPanel* floater = ((LLFloaterIMPanel*) data); | ||
812 | |||
813 | if ( floater ) floater->onClose(FALSE); | ||
814 | } | ||
815 | |||
816 | class LLViewerIMSessionStartReply : public LLHTTPNode | ||
817 | { | ||
818 | public: | ||
819 | virtual void describe(Description& desc) const | ||
820 | { | ||
821 | desc.shortInfo("Used for receiving a reply to a request to initialize an IM session"); | ||
822 | desc.postAPI(); | ||
823 | desc.input( | ||
824 | "{\"client_session_id\": UUID, \"session_id\": UUID, \"success\" boolean, \"reason\": string"); | ||
825 | desc.source(__FILE__, __LINE__); | ||
826 | } | ||
827 | |||
828 | virtual void post(ResponsePtr response, | ||
829 | const LLSD& context, | ||
830 | const LLSD& input) const | ||
831 | { | ||
832 | LLSD body; | ||
833 | LLUUID temp_session_id; | ||
834 | LLUUID session_id; | ||
835 | bool success; | ||
836 | |||
837 | body = input["body"]; | ||
838 | success = body["success"].asBoolean(); | ||
839 | temp_session_id = body["temp_session_id"].asUUID(); | ||
840 | |||
841 | if ( success ) | ||
842 | { | ||
843 | session_id = body["session_id"].asUUID(); | ||
844 | gIMView->updateFloaterSessionID(temp_session_id, | ||
845 | session_id); | ||
846 | } | ||
847 | else | ||
848 | { | ||
849 | //throw an error dialog and close the temp session's | ||
850 | //floater | ||
851 | LLFloaterIMPanel* floater = | ||
852 | gIMView->findFloaterBySession(temp_session_id); | ||
853 | if (floater) | ||
854 | { | ||
855 | LLString::format_map_t args; | ||
856 | args["[REASON]"] = | ||
857 | sErrorStringsMap[body["error"].asString()]; | ||
858 | args["[RECIPIENT]"] = floater->getTitle(); | ||
859 | |||
860 | gViewerWindow->alertXml("IMSessionStartError", | ||
861 | args, | ||
862 | onConfirmForceCloseError, | ||
863 | floater); | ||
864 | |||
865 | } | ||
866 | } | ||
867 | } | ||
868 | }; | ||
869 | |||
870 | class LLViewerIMSessionEventReply : public LLHTTPNode | ||
871 | { | ||
872 | public: | ||
873 | virtual void describe(Description& desc) const | ||
874 | { | ||
875 | desc.shortInfo("Used for receiving a reply to a IM session event"); | ||
876 | desc.postAPI(); | ||
877 | desc.input( | ||
878 | "{\"event\": string, \"reason\": string, \"success\": boolean, \"session_id\": UUID"); | ||
879 | desc.source(__FILE__, __LINE__); | ||
880 | } | ||
881 | |||
882 | virtual void post(ResponsePtr response, | ||
883 | const LLSD& context, | ||
884 | const LLSD& input) const | ||
885 | { | ||
886 | LLUUID session_id; | ||
887 | bool success; | ||
888 | |||
889 | LLSD body = input["body"]; | ||
890 | success = body["success"].asBoolean(); | ||
891 | session_id = body["session_id"].asUUID(); | ||
892 | |||
893 | if ( !success ) | ||
894 | { | ||
895 | //throw an error dialog | ||
896 | LLFloaterIMPanel* floater = | ||
897 | gIMView->findFloaterBySession(session_id); | ||
898 | if (floater) | ||
899 | { | ||
900 | LLString::format_map_t args; | ||
901 | args["[REASON]"] = | ||
902 | sErrorStringsMap[body["error"].asString()]; | ||
903 | args["[EVENT]"] = | ||
904 | sEventStringsMap[body["event"].asString()]; | ||
905 | args["[RECIPIENT]"] = floater->getTitle(); | ||
906 | |||
907 | gViewerWindow->alertXml("IMSessionEventError", | ||
908 | args); | ||
909 | } | ||
910 | } | ||
911 | } | ||
912 | }; | ||
913 | |||
914 | class LLViewerForceCloseIMSession: public LLHTTPNode | ||
915 | { | ||
916 | |||
917 | virtual void post(ResponsePtr response, | ||
918 | const LLSD& context, | ||
919 | const LLSD& input) const | ||
920 | { | ||
921 | LLUUID session_id; | ||
922 | LLString reason; | ||
923 | |||
924 | session_id = input["body"]["session_id"].asUUID(); | ||
925 | reason = input["body"]["reason"].asString(); | ||
926 | |||
927 | LLFloaterIMPanel* floater = | ||
928 | gIMView ->findFloaterBySession(session_id); | ||
929 | |||
930 | if ( floater ) | ||
931 | { | ||
932 | LLString::format_map_t args; | ||
933 | |||
934 | args["[NAME]"] = floater->getTitle(); | ||
935 | args["[REASON]"] = sForceCloseSessionMap[reason]; | ||
936 | |||
937 | gViewerWindow->alertXml("ForceCloseIMSession", | ||
938 | args, | ||
939 | onConfirmForceCloseError, | ||
940 | floater); | ||
941 | } | ||
942 | } | ||
943 | }; | ||
944 | |||
945 | LLHTTPRegistration<LLViewerIMSessionStartReply> | ||
946 | gHTTPRegistrationMessageImsessionstartreply("/message/IMSessionStartReply"); | ||
947 | |||
948 | LLHTTPRegistration<LLViewerIMSessionEventReply> | ||
949 | gHTTPRegistrationMessageImsessioneventreply("/message/IMSessionEventReply"); | ||
950 | |||
951 | LLHTTPRegistration<LLViewerForceCloseIMSession> | ||
952 | gHTTPRegistrationMessageForceCloseImSession("/message/ForceCloseIMSession"); | ||