├── .gradle ├── 4.4 │ ├── fileChanges │ │ └── last-build.bin │ ├── fileContent │ │ └── fileContent.lock │ ├── fileHashes │ │ ├── fileHashes.bin │ │ ├── fileHashes.lock │ │ └── resourceHashesCache.bin │ ├── javaCompile │ │ ├── classAnalysis.bin │ │ ├── jarAnalysis.bin │ │ ├── javaCompile.lock │ │ ├── taskHistory.bin │ │ └── taskJars.bin │ └── taskHistory │ │ ├── taskHistory.bin │ │ └── taskHistory.lock └── buildOutputCleanup │ ├── buildOutputCleanup.lock │ ├── cache.properties │ └── outputFiles.bin ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── gradle.xml ├── libraries │ ├── Gradle__com_android_support_appcompat_v7_23_1_1.xml │ ├── Gradle__com_android_support_design_23_1_1.xml │ ├── Gradle__com_android_support_recyclerview_v7_23_1_1.xml │ ├── Gradle__com_android_support_support_annotations_23_1_1_jar.xml │ ├── Gradle__com_android_support_support_v4_23_1_1.xml │ ├── Gradle__de_measite_minidns_minidns_0_1_7_jar.xml │ ├── Gradle__io_pristine_libjingle_9127.xml │ ├── Gradle__org_igniterealtime_smack_smack_android_4_1_0_jar.xml │ ├── Gradle__org_igniterealtime_smack_smack_android_extensions_4_1_0_jar.xml │ ├── Gradle__org_igniterealtime_smack_smack_core_4_1_0_jar.xml │ ├── Gradle__org_igniterealtime_smack_smack_extensions_4_1_0_jar.xml │ ├── Gradle__org_igniterealtime_smack_smack_im_4_1_0_jar.xml │ ├── Gradle__org_igniterealtime_smack_smack_resolver_minidns_4_1_0_jar.xml │ ├── Gradle__org_igniterealtime_smack_smack_sasl_provided_4_1_0_jar.xml │ ├── Gradle__org_igniterealtime_smack_smack_tcp_4_1_0_jar.xml │ ├── Gradle__org_jxmpp_jxmpp_core_0_4_2_beta1_jar.xml │ ├── Gradle__org_jxmpp_jxmpp_util_cache_0_4_2_beta1_jar.xml │ └── Gradle__xpp3_xpp3_1_1_4c_jar.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml ├── vcs.xml └── workspace.xml ├── .svn ├── entries ├── format ├── wc.db └── wc.db-journal ├── README.md ├── app ├── .gitignore ├── app.iml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── activity │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ ├── CustomXML │ │ │ ├── IceCandidateExtensionElement.java │ │ │ ├── SDPExtensionElement.java │ │ │ └── VideoInvitation.java │ │ │ ├── MyApplication.java │ │ │ ├── activity │ │ │ ├── AddFriendActivity.java │ │ │ ├── ChatActivity.java │ │ │ ├── ChatServiceActivity.java │ │ │ ├── LoginActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── MultiUserChatRoomActivity.java │ │ │ ├── MultiUserChatRoomsActivity.java │ │ │ ├── ParentActivity.java │ │ │ ├── RegisterActivity.java │ │ │ └── VideoCallActivity.java │ │ │ ├── adapter │ │ │ ├── ChatListAdapter.java │ │ │ ├── ChatServiceListAdapter.java │ │ │ ├── FriendListAdapter.java │ │ │ ├── MultiUserChatRoomListAdapter.java │ │ │ └── ParentAdapter.java │ │ │ ├── common │ │ │ ├── Const.java │ │ │ ├── Storage.java │ │ │ ├── Util.java │ │ │ └── XMPPUtil.java │ │ │ └── data │ │ │ ├── ChatData.java │ │ │ ├── ChatRoomData.java │ │ │ ├── ChatServiceData.java │ │ │ ├── DataWarehouse.java │ │ │ ├── LoginData.java │ │ │ └── UserData.java │ └── res │ │ ├── drawable │ │ └── side_nav_bar.xml │ │ ├── layout │ │ ├── activity_add_friend.xml │ │ ├── activity_chat.xml │ │ ├── activity_chat_service.xml │ │ ├── activity_face.xml │ │ ├── activity_login.xml │ │ ├── activity_main.xml │ │ ├── activity_multi_user_chat_room.xml │ │ ├── activity_multi_user_chat_rooms.xml │ │ ├── activity_register.xml │ │ ├── activity_video_call.xml │ │ ├── chat_list_item.xml │ │ ├── chat_service_item.xml │ │ ├── friend_list_item.xml │ │ └── multi_user_chat_room_item.xml │ │ ├── menu │ │ ├── activity_navigation_drawer.xml │ │ ├── friend_list_context_menu.xml │ │ ├── menu_chat.xml │ │ ├── menu_chat_room.xml │ │ ├── menu_main.xml │ │ └── navigation.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── drawables.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── smack │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── local.properties ├── settings.gradle └── webrtc_test.iml /.gradle/4.4/fileChanges/last-build.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gradle/4.4/fileContent/fileContent.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.gradle/4.4/fileContent/fileContent.lock -------------------------------------------------------------------------------- /.gradle/4.4/fileHashes/fileHashes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.gradle/4.4/fileHashes/fileHashes.bin -------------------------------------------------------------------------------- /.gradle/4.4/fileHashes/fileHashes.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.gradle/4.4/fileHashes/fileHashes.lock -------------------------------------------------------------------------------- /.gradle/4.4/fileHashes/resourceHashesCache.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.gradle/4.4/fileHashes/resourceHashesCache.bin -------------------------------------------------------------------------------- /.gradle/4.4/javaCompile/classAnalysis.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.gradle/4.4/javaCompile/classAnalysis.bin -------------------------------------------------------------------------------- /.gradle/4.4/javaCompile/jarAnalysis.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.gradle/4.4/javaCompile/jarAnalysis.bin -------------------------------------------------------------------------------- /.gradle/4.4/javaCompile/javaCompile.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.gradle/4.4/javaCompile/javaCompile.lock -------------------------------------------------------------------------------- /.gradle/4.4/javaCompile/taskHistory.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.gradle/4.4/javaCompile/taskHistory.bin -------------------------------------------------------------------------------- /.gradle/4.4/javaCompile/taskJars.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.gradle/4.4/javaCompile/taskJars.bin -------------------------------------------------------------------------------- /.gradle/4.4/taskHistory/taskHistory.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.gradle/4.4/taskHistory/taskHistory.bin -------------------------------------------------------------------------------- /.gradle/4.4/taskHistory/taskHistory.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.gradle/4.4/taskHistory/taskHistory.lock -------------------------------------------------------------------------------- /.gradle/buildOutputCleanup/buildOutputCleanup.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.gradle/buildOutputCleanup/buildOutputCleanup.lock -------------------------------------------------------------------------------- /.gradle/buildOutputCleanup/cache.properties: -------------------------------------------------------------------------------- 1 | #Tue Sep 11 14:06:34 CST 2018 2 | gradle.version=4.4 3 | -------------------------------------------------------------------------------- /.gradle/buildOutputCleanup/outputFiles.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.gradle/buildOutputCleanup/outputFiles.bin -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_appcompat_v7_23_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_design_23_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_recyclerview_v7_23_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_support_annotations_23_1_1_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_android_support_support_v4_23_1_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__de_measite_minidns_minidns_0_1_7_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__io_pristine_libjingle_9127.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_igniterealtime_smack_smack_android_4_1_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_igniterealtime_smack_smack_android_extensions_4_1_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_igniterealtime_smack_smack_core_4_1_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_igniterealtime_smack_smack_extensions_4_1_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_igniterealtime_smack_smack_im_4_1_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_igniterealtime_smack_smack_resolver_minidns_4_1_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_igniterealtime_smack_smack_sasl_provided_4_1_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_igniterealtime_smack_smack_tcp_4_1_0_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_jxmpp_jxmpp_core_0_4_2_beta1_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_jxmpp_jxmpp_util_cache_0_4_2_beta1_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__xpp3_xpp3_1_1_4c_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 37 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.svn/entries: -------------------------------------------------------------------------------- 1 | 12 2 | -------------------------------------------------------------------------------- /.svn/format: -------------------------------------------------------------------------------- 1 | 12 2 | -------------------------------------------------------------------------------- /.svn/wc.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.svn/wc.db -------------------------------------------------------------------------------- /.svn/wc.db-journal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/by666/LocalVideo_Android/d4f4e1b825a0f539658452eab22260fa6a31e6b6/.svn/wc.db-journal -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/activity/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.example.activity; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/CustomXML/IceCandidateExtensionElement.java: -------------------------------------------------------------------------------- 1 | package com.example.CustomXML; 2 | 3 | import org.jivesoftware.smack.packet.ExtensionElement; 4 | 5 | 6 | public class IceCandidateExtensionElement implements ExtensionElement { 7 | public static final String NAME_SPACE = "com.webrtc.ice_candidate"; 8 | public static final String ELEMENT_NAME = "ice_candidate"; 9 | 10 | //代表public final String sdpMid; 11 | private String sdpMidElement = "sdpMid"; 12 | private String sdpMidText = ""; 13 | 14 | //代表public final int sdpMLineIndex; 15 | private String sdpMLineIndexElement = "sdpMLineIndex"; 16 | private int sdpMLineIndexText = 0; 17 | 18 | //代表public final String sdp; 19 | private String sdpElement = "sdp"; 20 | private String sdpText = ""; 21 | 22 | public String getSdpMidText() { 23 | return sdpMidText; 24 | } 25 | 26 | public void setSdpMidText(String sdpMidText) { 27 | this.sdpMidText = sdpMidText; 28 | } 29 | 30 | public int getSdpMLineIndexText() { 31 | return sdpMLineIndexText; 32 | } 33 | 34 | public void setSdpMLineIndexText(int sdpMLineIndexText) { 35 | this.sdpMLineIndexText = sdpMLineIndexText; 36 | } 37 | 38 | public String getSdpText() { 39 | return sdpText; 40 | } 41 | 42 | public void setSdpText(String sdpText) { 43 | this.sdpText = sdpText; 44 | } 45 | 46 | @Override 47 | public String getNamespace() { 48 | return NAME_SPACE; 49 | } 50 | 51 | @Override 52 | public String getElementName() { 53 | return ELEMENT_NAME; 54 | } 55 | 56 | @Override 57 | public CharSequence toXML() { 58 | StringBuilder sb = new StringBuilder(); 59 | 60 | /* 61 | * sdpMidText 62 | * sdpMLineIndexText 63 | * sdpText 64 | **/ 65 | sb.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAME_SPACE).append("\">"); 66 | sb.append("<" + sdpMidElement + ">").append(sdpMidText).append(""); 67 | sb.append("<" + sdpMLineIndexElement + ">").append(sdpMLineIndexText).append(""); 68 | sb.append("<" + sdpElement + ">").append(sdpText).append(""); 69 | sb.append(""); 70 | 71 | return sb.toString(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/CustomXML/SDPExtensionElement.java: -------------------------------------------------------------------------------- 1 | package com.example.CustomXML; 2 | 3 | import org.jivesoftware.smack.packet.ExtensionElement; 4 | 5 | public class SDPExtensionElement implements ExtensionElement { 6 | public static final String NAME_SPACE = "com.webrtc.sdp"; 7 | public static final String ELEMENT_NAME = "sdp"; 8 | 9 | //代表SessionDescription.Type type 10 | private String typeElement = "type"; 11 | private String typeText = ""; 12 | 13 | //代表 String description 14 | private String descriptionElement = "description"; 15 | private String descriptionText = ""; 16 | 17 | public String getTypeText() { 18 | return typeText; 19 | } 20 | 21 | public void setTypeText(String typeText) { 22 | this.typeText = typeText; 23 | } 24 | 25 | public String getDescriptionText() { 26 | return descriptionText; 27 | } 28 | 29 | public void setDescriptionText(String descriptionText) { 30 | this.descriptionText = descriptionText; 31 | } 32 | 33 | @Override 34 | public String getNamespace() { 35 | return NAME_SPACE; 36 | } 37 | 38 | @Override 39 | public String getElementName() { 40 | return ELEMENT_NAME; 41 | } 42 | 43 | @Override 44 | public CharSequence toXML() { 45 | StringBuilder sb = new StringBuilder(); 46 | 47 | /* 48 | * typeText 49 | * descriptionText 50 | **/ 51 | sb.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAME_SPACE).append("\">"); 52 | sb.append("<" + typeElement + ">").append(typeText).append(""); 53 | sb.append("<" + descriptionElement + ">").append(descriptionText).append(""); 54 | sb.append(""); 55 | 56 | return sb.toString(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/CustomXML/VideoInvitation.java: -------------------------------------------------------------------------------- 1 | package com.example.CustomXML; 2 | 3 | import org.jivesoftware.smack.packet.ExtensionElement; 4 | 5 | 6 | public class VideoInvitation implements ExtensionElement { 7 | public static final String NAME_SPACE = "com.webrtc.video"; 8 | public static final String ELEMENT_NAME = "video-chat"; 9 | 10 | private String typeElement = "type"; 11 | 12 | private String typeText = ""; 13 | 14 | @Override 15 | public String getNamespace() { 16 | return NAME_SPACE; 17 | } 18 | 19 | @Override 20 | public String getElementName() { 21 | return ELEMENT_NAME; 22 | } 23 | 24 | @Override 25 | public CharSequence toXML() { 26 | StringBuilder sb = new StringBuilder(); 27 | 28 | /* 29 | * typeText 30 | **/ 31 | sb.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAME_SPACE).append("\">"); 32 | sb.append("<" + typeElement + ">").append(typeText).append(""); 33 | sb.append(""); 34 | 35 | return sb.toString(); 36 | } 37 | 38 | public String getTypeText() { 39 | return typeText; 40 | } 41 | 42 | public void setTypeText(String typeText) { 43 | this.typeText = typeText; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import android.Manifest; 4 | import android.app.Application; 5 | import android.content.pm.PackageManager; 6 | import android.support.v4.app.ActivityCompat; 7 | 8 | import com.example.data.LoginData; 9 | 10 | import org.jivesoftware.smack.tcp.XMPPTCPConnection; 11 | 12 | import java.util.Set; 13 | import java.util.TreeSet; 14 | 15 | public class MyApplication extends Application{ 16 | public XMPPTCPConnection xmpptcpConnection; 17 | //记录登录信息 18 | public LoginData loginData = new LoginData(); 19 | //记录当前正在聊天的用户 20 | public Set chatUsers = new TreeSet<>(); 21 | 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/activity/AddFriendActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.activity; 2 | 3 | import android.content.Intent; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.EditText; 9 | import android.widget.Toast; 10 | 11 | import org.jivesoftware.smack.roster.Roster; 12 | 13 | public class AddFriendActivity extends ParentActivity { 14 | private EditText mEditTextFriendUsername; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_add_friend); 20 | 21 | mEditTextFriendUsername = (EditText) findViewById(R.id.frendUsername); 22 | 23 | 24 | } 25 | 26 | public void onClickAddFriend(View view) { 27 | String account = mEditTextFriendUsername.getText().toString().trim(); 28 | 29 | if("".equals(account)) 30 | { 31 | Toast.makeText(this,"请输入账号",Toast.LENGTH_SHORT).show(); 32 | return; 33 | } 34 | 35 | 36 | try { 37 | //添加好友 38 | Roster.getInstanceFor(connection).createEntry(account, "", null); 39 | Intent intent = new Intent(); 40 | intent.putExtra("friendName",account); 41 | setResult(1, intent); 42 | Toast.makeText(this,"添加好友成功!",Toast.LENGTH_SHORT).show(); 43 | finish(); 44 | } 45 | catch (Exception e) 46 | { 47 | e.printStackTrace(); 48 | Toast.makeText(this,"添加好友失败("+e.getMessage()+")",Toast.LENGTH_SHORT).show(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/activity/ChatActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.activity; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Matrix; 7 | import android.os.Bundle; 8 | import android.text.SpannableString; 9 | import android.text.Spanned; 10 | import android.text.style.ImageSpan; 11 | import android.util.Log; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | import android.view.View; 15 | import android.widget.EditText; 16 | import android.widget.ListView; 17 | import android.widget.Toast; 18 | 19 | import com.example.CustomXML.VideoInvitation; 20 | import com.example.adapter.ChatListAdapter; 21 | import com.example.common.Const; 22 | import com.example.common.Util; 23 | import com.example.data.ChatData; 24 | import com.example.data.DataWarehouse; 25 | import com.example.data.LoginData; 26 | 27 | import org.jivesoftware.smack.SmackException; 28 | import org.jivesoftware.smack.chat.Chat; 29 | import org.jivesoftware.smack.chat.ChatManager; 30 | import org.jivesoftware.smack.packet.Message; 31 | 32 | public class ChatActivity extends ParentActivity implements Const{ 33 | protected String mFriendUsername; //聊天用户名称 34 | private String mServiceName; //XMPP服务器名称 35 | 36 | private LoginData mLoginData; //当前账号登录信息 37 | 38 | private EditText mEditTextChatText; //聊天文本框 39 | protected ListView mListViewChatList; //显示聊天记录 40 | 41 | protected ChatListAdapter mChatListAdapter; 42 | 43 | // private MyHandler myHandler; 44 | 45 | // Handler mHandler; 46 | 47 | @Override 48 | protected void onCreate(Bundle savedInstanceState) { 49 | super.onCreate(savedInstanceState); 50 | setContentView(R.layout.activity_chat); 51 | 52 | mServiceName = connection.getServiceName(); 53 | Log.e("ServiceName","ChatActivity"+mServiceName); 54 | 55 | mLoginData = DataWarehouse.getGlobalData(this).loginData; 56 | 57 | mEditTextChatText = (EditText) findViewById(R.id.edittext_chat_text); 58 | mListViewChatList = (ListView) findViewById(R.id.listview_ChatList); 59 | 60 | mChatListAdapter = new ChatListAdapter(this); 61 | mListViewChatList.setAdapter(mChatListAdapter); 62 | //隐藏分隔条 63 | mListViewChatList.setDivider(null); 64 | 65 | //处理发送过来的聊天信息等。 66 | processExtraData(); 67 | } 68 | 69 | @Override 70 | protected void onNewIntent(Intent intent) { 71 | super.onNewIntent(intent); 72 | //must store the new intent unless getIntent() will return the old one 73 | setIntent(intent); 74 | processExtraData(); 75 | } 76 | 77 | private void processExtraData() 78 | { 79 | Intent intent = getIntent(); 80 | if (mFriendUsername == null) { 81 | mFriendUsername = intent.getStringExtra("friendUsername"); 82 | } 83 | 84 | 85 | String body = intent.getStringExtra("body"); 86 | if (body != null) 87 | { 88 | ChatData item = new ChatData(); 89 | item.text = body; 90 | item.user = mFriendUsername; 91 | mChatListAdapter.addItem(item); 92 | mListViewChatList.setSelection(mListViewChatList.getAdapter().getCount()-1); 93 | } 94 | 95 | DataWarehouse.getGlobalData(this).chatUsers.add(mFriendUsername); 96 | } 97 | 98 | 99 | @Override 100 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 101 | switch (requestCode) 102 | { 103 | case 1: 104 | int faceId = data.getIntExtra(KEY_FACE_ID, -1); 105 | if (faceId != -1) 106 | { 107 | String faceResName = FACE_PREFIX + faceId; 108 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), 109 | Util.getResourceIDFromName(R.drawable.class,faceResName)); 110 | //将表情缩小到原来的60% 111 | Matrix matrix = new Matrix(); 112 | matrix.postScale(0.6f,0.6f); 113 | Bitmap smallBitmap = Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(), 114 | bitmap.getHeight(),matrix,true); 115 | ImageSpan imageSpan = new ImageSpan(this,smallBitmap); 116 | //表情图片代表的表情文本,EditText接收到的表情是表情文本如<:10:>,要想显示 117 | //表情图片需要将表情文本转换为表情图片,转换函数为Util.updateFacesForTextView() 118 | String faceText = FACE_TEXT_PREFIX + faceId + FACE_TEXT_SUFFIX; 119 | SpannableString spannableString = new SpannableString(faceText); 120 | spannableString.setSpan(imageSpan, 0, faceText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 121 | 122 | //插入表情图片 123 | mEditTextChatText.getText().insert(mEditTextChatText.getSelectionStart(), spannableString); 124 | 125 | } 126 | break; 127 | } 128 | } 129 | 130 | public void onClick_Send(View view) { 131 | String chatText = mEditTextChatText.getText().toString().trim(); 132 | if (!"".equals(chatText)) 133 | { 134 | String chatJid = mFriendUsername+"@"+mServiceName; 135 | Message message = new Message(chatJid,Message.Type.chat); 136 | message.setBody(chatText); 137 | Chat chat = createChat(chatJid); 138 | 139 | try { 140 | chat.sendMessage(message); 141 | mEditTextChatText.setText(""); 142 | } catch (SmackException.NotConnectedException e) { 143 | e.printStackTrace(); 144 | } 145 | //显示发送聊天信息 146 | ChatData item = new ChatData(); 147 | item.user = mLoginData.userName; //没有设置别名的功能。 148 | item.text = chatText; 149 | item.isOwner = true; 150 | 151 | mChatListAdapter.addItem(item); 152 | //滚动到消息的最后一条。 153 | mListViewChatList.setSelection(mListViewChatList.getAdapter().getCount()-1); 154 | 155 | } 156 | else 157 | { 158 | Toast.makeText(this,"发送消息不能为空",Toast.LENGTH_SHORT).show(); 159 | } 160 | 161 | } 162 | 163 | 164 | @Override 165 | protected void onDestroy() { 166 | super.onDestroy(); 167 | //当当前Activity销毁时,关闭监听对象。 168 | DataWarehouse.getGlobalData(this).chatUsers.remove(mFriendUsername); 169 | } 170 | 171 | @Override 172 | public boolean onCreateOptionsMenu(Menu menu) { 173 | super.onCreateOptionsMenu(menu); 174 | getMenuInflater().inflate(R.menu.menu_chat, menu); 175 | return true; 176 | } 177 | 178 | @Override 179 | public boolean onOptionsItemSelected(MenuItem item) { 180 | int id = item.getItemId(); 181 | 182 | switch (id) 183 | { 184 | //向对方发送视频通话请求 185 | case R.id.request_video: 186 | String chatJid = mFriendUsername+"@"+mServiceName; 187 | Message message = new Message(); 188 | 189 | VideoInvitation videoInvitation = new VideoInvitation(); 190 | videoInvitation.setTypeText("video-invitation"); 191 | message.addExtension(videoInvitation); 192 | Chat chat = createChat(chatJid); 193 | 194 | try { 195 | chat.sendMessage(message); 196 | } catch (SmackException.NotConnectedException e) { 197 | e.printStackTrace(); 198 | } 199 | break; 200 | } 201 | 202 | return super.onOptionsItemSelected(item); 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/activity/ChatServiceActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.activity; 2 | 3 | import android.content.Intent; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.AdapterView; 8 | import android.widget.ListView; 9 | 10 | import com.example.adapter.ChatServiceListAdapter; 11 | import com.example.common.Const; 12 | 13 | import org.jivesoftware.smack.SmackException; 14 | import org.jivesoftware.smack.XMPPException; 15 | import org.jivesoftware.smackx.muc.HostedRoom; 16 | import org.jivesoftware.smackx.muc.MultiUserChat; 17 | import org.jivesoftware.smackx.muc.MultiUserChatManager; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collection; 21 | import java.util.List; 22 | 23 | public class ChatServiceActivity extends ParentActivity implements Const{ 24 | private ListView listView; 25 | private ChatServiceListAdapter chatServiceListAdapter; 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_chat_service); 30 | 31 | listView = (ListView) findViewById(R.id.listview_chat_services); 32 | chatServiceListAdapter = new ChatServiceListAdapter(this,getHostedRooms()); 33 | listView.setAdapter(chatServiceListAdapter); 34 | 35 | listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 36 | @Override 37 | public void onItemClick(AdapterView parent, View view, int position, long id) { 38 | String jid = chatServiceListAdapter.getJID(position); 39 | Intent intent = new Intent(ChatServiceActivity.this,MultiUserChatRoomsActivity.class); 40 | intent.putExtra(JID,jid); 41 | startActivity(intent); 42 | } 43 | }); 44 | } 45 | 46 | private List getHostedRooms() 47 | { 48 | 49 | List chatServices = new ArrayList<>(); 50 | 51 | try { 52 | chatServices = MultiUserChatManager.getInstanceFor(connection). 53 | getHostedRooms(connection.getServiceName()); 54 | } catch (SmackException.NoResponseException e) { 55 | e.printStackTrace(); 56 | } catch (XMPPException.XMPPErrorException e) { 57 | e.printStackTrace(); 58 | } catch (SmackException.NotConnectedException e) { 59 | e.printStackTrace(); 60 | } 61 | return chatServices; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/activity/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.activity; 2 | 3 | import android.Manifest; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.support.v4.app.ActivityCompat; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.util.Log; 12 | import android.view.View; 13 | import android.widget.CheckBox; 14 | import android.widget.EditText; 15 | import android.widget.Toast; 16 | 17 | import com.example.common.Const; 18 | import com.example.common.Storage; 19 | import com.example.common.XMPPUtil; 20 | import com.example.data.DataWarehouse; 21 | import com.example.data.LoginData; 22 | 23 | public class LoginActivity extends AppCompatActivity implements Const{ 24 | private EditText mEditTextUsername; 25 | private EditText mEditTextPassword; 26 | 27 | private LoginData mLoginDate; 28 | 29 | private Handler handler; 30 | private Context context; 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | 35 | setContentView(R.layout.activity_login); 36 | 37 | context = this; 38 | mEditTextUsername = (EditText) findViewById(R.id.username); 39 | mEditTextPassword = (EditText) findViewById(R.id.password); 40 | 41 | mLoginDate = DataWarehouse.getGlobalData(this).loginData; 42 | 43 | handler = new Handler(); 44 | 45 | mLoginDate.userName = Storage.getString(this,KEY_USERNAME); 46 | mLoginDate.passWord = Storage.getString(this,KEY_PASSWORD); 47 | 48 | mEditTextUsername.setText(mLoginDate.userName); 49 | mEditTextPassword.setText(mLoginDate.passWord); 50 | 51 | checkPermissions(); 52 | 53 | } 54 | 55 | //登录按钮的单击事件方法 56 | public void onClickLogin(View view) { 57 | //将登录信息保存到全局对象中,当然也可以从SharedPreferences中读取, 58 | // 但是比较麻烦,所以保存到全局变量中。 59 | mLoginDate.userName = mEditTextUsername.getText().toString(); 60 | mLoginDate.passWord = mEditTextPassword.getText().toString(); 61 | 62 | if (mLoginDate.userName.equals(null) || mLoginDate.userName.equals("")) 63 | { 64 | Toast.makeText(LoginActivity.this,"请输入用户名",Toast.LENGTH_SHORT).show(); 65 | return; 66 | } 67 | if (mLoginDate.passWord.equals(null) || mLoginDate.passWord.equals("")) 68 | { 69 | Toast.makeText(LoginActivity.this,"请输入密码",Toast.LENGTH_SHORT).show(); 70 | return; 71 | } 72 | Log.e("username",mLoginDate.userName); 73 | Log.e("password",mLoginDate.passWord); 74 | 75 | //存储登录信息。 76 | /*为了存储的这些数据可以在别的地方应用,故需要将Key定义为常量,以便其他地方引用。 77 | *将Key保存在接口中以便其它类引用。 78 | * 当然,也可以定义在一个类中,但是需要将Key定义为静态变量,比较麻烦,故将其定义在接口中。 79 | * 将Key保存在接口Const中,所有的Key名称以Key_开头。 80 | */ 81 | Storage.putString(this,KEY_USERNAME,mLoginDate.userName); 82 | Storage.putString(this,KEY_PASSWORD,mLoginDate.passWord); 83 | 84 | //新版本中不允许在主线程中直接访问网络。故登录服务器需要另起一个线程。 85 | new Thread(new Runnable() { 86 | @Override 87 | public void run() { 88 | //登录成功 89 | if (XMPPUtil.login(context,mLoginDate.userName,mLoginDate.passWord)) 90 | { 91 | 92 | Intent intent = new Intent(LoginActivity.this,MainActivity.class); 93 | startActivity(intent); 94 | finish(); 95 | } 96 | else //登录失败 97 | { 98 | handler.post(new Runnable() { 99 | @Override 100 | public void run() { 101 | Toast.makeText(LoginActivity.this,"登录失败,请检查用户名和密码的正确性",Toast.LENGTH_SHORT).show(); 102 | } 103 | }); 104 | } 105 | } 106 | }).start(); 107 | } 108 | 109 | public void onClickRegister(View view) { 110 | Intent intent = new Intent(LoginActivity.this,RegisterActivity.class); 111 | startActivity(intent); 112 | } 113 | 114 | private void checkPermissions() { 115 | 116 | int REQUEST_EXTERNAL_STORAGE = 1; 117 | String[] PERMISSIONS_STORAGE = { 118 | Manifest.permission.READ_EXTERNAL_STORAGE, 119 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 120 | Manifest.permission.RECORD_AUDIO, 121 | Manifest.permission.ACCESS_WIFI_STATE, 122 | Manifest.permission.INTERNET, 123 | Manifest.permission.ACCESS_NETWORK_STATE, 124 | Manifest.permission.ACCESS_WIFI_STATE, 125 | Manifest.permission.ACCESS_COARSE_LOCATION, 126 | Manifest.permission.CHANGE_WIFI_STATE, 127 | Manifest.permission.CAMERA, 128 | Manifest.permission.READ_PHONE_STATE, 129 | Manifest.permission.MODIFY_AUDIO_SETTINGS 130 | }; 131 | int permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); 132 | if (permission != PackageManager.PERMISSION_GRANTED) { 133 | // We don't have permission so prompt the user 134 | ActivityCompat.requestPermissions( 135 | this, 136 | PERMISSIONS_STORAGE, 137 | REQUEST_EXTERNAL_STORAGE 138 | ); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.activity; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.util.Log; 10 | import android.view.ContextMenu; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | import android.view.View; 14 | import android.widget.AdapterView; 15 | import android.widget.ListView; 16 | import android.widget.Toast; 17 | 18 | import com.example.CustomXML.IceCandidateExtensionElement; 19 | import com.example.CustomXML.SDPExtensionElement; 20 | import com.example.CustomXML.VideoInvitation; 21 | import com.example.adapter.FriendListAdapter; 22 | import com.example.common.Const; 23 | import com.example.common.Storage; 24 | import com.example.common.Util; 25 | import com.example.data.DataWarehouse; 26 | import com.example.data.LoginData; 27 | import com.example.data.UserData; 28 | 29 | import org.jivesoftware.smack.SmackException; 30 | import org.jivesoftware.smack.chat.Chat; 31 | import org.jivesoftware.smack.chat.ChatManagerListener; 32 | import org.jivesoftware.smack.chat.ChatMessageListener; 33 | import org.jivesoftware.smack.packet.DefaultExtensionElement; 34 | import org.jivesoftware.smack.packet.Message; 35 | import org.jivesoftware.smack.roster.Roster; 36 | 37 | import java.util.Set; 38 | 39 | public class MainActivity extends ParentActivity { 40 | private ListView mListViewFriend; 41 | private Set mChatUsers; 42 | 43 | public static FriendListAdapter mFriendListAdapter; 44 | 45 | private MyHandler myHandler; 46 | private int mCurrentPosition = -1; 47 | private String mServiceName; 48 | 49 | 50 | @Override 51 | protected void onCreate(Bundle savedInstanceState) { 52 | super.onCreate(savedInstanceState); 53 | setContentView(R.layout.activity_main); 54 | 55 | mServiceName = connection.getServiceName(); 56 | 57 | mListViewFriend = (ListView) findViewById(R.id.listview_friends); 58 | 59 | mFriendListAdapter = new FriendListAdapter(this, Roster.getInstanceFor(connection).getEntries()); 60 | 61 | mChatUsers = DataWarehouse.getGlobalData(this).chatUsers; 62 | 63 | mListViewFriend.setAdapter(mFriendListAdapter); 64 | 65 | //点击进入聊天窗口 66 | mListViewFriend.setOnItemClickListener(new AdapterView.OnItemClickListener() { 67 | @Override 68 | public void onItemClick(AdapterView parent, View view, int position, long id) { 69 | Intent intent = new Intent(MainActivity.this, ChatActivity.class); 70 | String friendUserame = mFriendListAdapter.getUsername(position); 71 | intent.putExtra("friendUsername", friendUserame); 72 | startActivity(intent); 73 | } 74 | }); 75 | 76 | //设置聊天对象管理处理监听。 77 | getChatManager().addChatListener(chatManagerListenerMain); 78 | 79 | myHandler = new MyHandler(this); 80 | 81 | registerForContextMenu(mListViewFriend); 82 | mListViewFriend.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { 83 | @Override 84 | public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { 85 | mCurrentPosition = position; 86 | return false; 87 | } 88 | }); 89 | } 90 | 91 | @Override 92 | public boolean onContextItemSelected(MenuItem item) { 93 | int id = item.getItemId(); 94 | Intent intent = null; 95 | switch (id) 96 | { 97 | case R.id.menu_item_remove_friend: 98 | if(mCurrentPosition > -1) 99 | { 100 | mFriendListAdapter.removeUserData(mCurrentPosition); 101 | } 102 | break; 103 | } 104 | return super.onContextItemSelected(item); 105 | } 106 | 107 | 108 | 109 | //创建上下文菜单时触发该方法。 110 | @Override 111 | public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { 112 | super.onCreateContextMenu(menu, v, menuInfo); 113 | getMenuInflater().inflate(R.menu.friend_list_context_menu,menu); 114 | } 115 | 116 | //创建聊天对象管理监听器,监听从远端发送过来的message。 117 | private ChatManagerListener chatManagerListenerMain = new ChatManagerListener() { 118 | @Override 119 | public void chatCreated(Chat chat, boolean b) { 120 | chat.addMessageListener(new ChatMessageListener() { 121 | @Override 122 | public void processMessage(Chat chat, Message message) { 123 | android.os.Message msg = android.os.Message.obtain(); 124 | msg.obj = message; 125 | myHandler.sendMessage(msg); 126 | 127 | } 128 | }); 129 | } 130 | }; 131 | 132 | //创建菜单 133 | @Override 134 | public boolean onCreateOptionsMenu(Menu menu) { 135 | super.onCreateOptionsMenu(menu); 136 | getMenuInflater().inflate(R.menu.menu_main,menu); 137 | return true; 138 | } 139 | 140 | //相应菜单 141 | @Override 142 | public boolean onOptionsItemSelected(MenuItem item) { 143 | int id = item.getItemId(); 144 | Intent intent = null; 145 | 146 | switch (id) 147 | { 148 | //聊天界面 149 | case R.id.add_friend: 150 | intent = new Intent(this,AddFriendActivity.class); 151 | startActivityForResult(intent,1); 152 | break; 153 | //聊天室服务界面,显示聊天室服务。 154 | case R.id.chat_services: 155 | intent = new Intent(this,ChatServiceActivity.class); 156 | startActivity(intent); 157 | break; 158 | case R.id.menu_item_logout: 159 | AlertDialog.Builder builder = new AlertDialog.Builder(this) 160 | .setTitle("注销") 161 | .setMessage("确定要注销当前用户吗?") 162 | .setPositiveButton("确定", new DialogInterface.OnClickListener() { 163 | @Override 164 | public void onClick(DialogInterface dialog, int which) { 165 | Storage.putBollean(MainActivity.this, Const.KEY_AUTO_LOGIN, false); 166 | try { 167 | connection.disconnect(); 168 | } 169 | catch (Exception e) 170 | { 171 | e.printStackTrace(); 172 | } 173 | Intent intent = new Intent(MainActivity.this, LoginActivity.class); 174 | startActivity(intent); 175 | finish(); 176 | } 177 | }) 178 | .setNegativeButton("取消",null); 179 | builder.show(); 180 | break; 181 | } 182 | return super.onOptionsItemSelected(item); 183 | } 184 | 185 | @Override 186 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 187 | super.onActivityResult(requestCode, resultCode, data); 188 | switch (requestCode) 189 | { 190 | case 1: 191 | if (resultCode == 1) 192 | { 193 | String username = data.getStringExtra("friendName"); 194 | 195 | UserData userData = new UserData(username); 196 | //实时刷新ListView 197 | mFriendListAdapter.addUserData(userData); 198 | } 199 | } 200 | } 201 | 202 | private static class MyHandler extends Handler { 203 | private Context mContext; 204 | private MainActivity mMainActivity; 205 | 206 | public MyHandler(Context mContext) { 207 | this.mContext = mContext; 208 | this.mMainActivity = (MainActivity) mContext; 209 | } 210 | 211 | @Override 212 | public void handleMessage(android.os.Message msg) { 213 | super.handleMessage(msg); 214 | Message message = (Message) msg.obj; 215 | final String friendUsername = Util.extractUserFromChat(message.getFrom()); 216 | //如果包含视频通话信息 217 | if (message.hasExtension(VideoInvitation.ELEMENT_NAME,VideoInvitation.NAME_SPACE)) 218 | { 219 | // video-invitation 220 | // Log.e("XML","XML: "+message.getExtension(VideoInvitation.NAME_SPACE).toXML()); 221 | 222 | DefaultExtensionElement defaultExtensionElement = message.getExtension(VideoInvitation.ELEMENT_NAME, 223 | VideoInvitation.NAME_SPACE); 224 | String type = defaultExtensionElement.getValue("type"); 225 | 226 | switch (type) 227 | { 228 | case "video-invitation": 229 | AlertDialog.Builder builder = new AlertDialog.Builder(mMainActivity) 230 | .setTitle("视频聊天") 231 | .setMessage(friendUsername + "请求与你视频聊天"); 232 | 233 | builder.setNegativeButton("拒绝", new DialogInterface.OnClickListener() { 234 | @Override 235 | public void onClick(DialogInterface dialog, int which) { 236 | //点击拒绝按钮需要做的事情。 237 | VideoInvitation videoInvitation = new VideoInvitation(); 238 | videoInvitation.setTypeText("video-deny"); 239 | String chatJid = friendUsername+"@"+mMainActivity.mServiceName; 240 | Message message = new Message(); 241 | message.addExtension(videoInvitation); 242 | 243 | Chat chat = mMainActivity.createChat(chatJid); 244 | try { 245 | chat.sendMessage(message); 246 | } catch (SmackException.NotConnectedException e) { 247 | e.printStackTrace(); 248 | } 249 | } 250 | }); 251 | 252 | //被叫方跳转到VideoCallActivity 253 | builder.setPositiveButton("接受", new DialogInterface.OnClickListener() { 254 | @Override 255 | public void onClick(DialogInterface dialog, int which) { 256 | //单击接受视频通话按钮后,处理的动作 257 | VideoInvitation videoInvitation = new VideoInvitation(); 258 | videoInvitation.setTypeText("video-agree"); 259 | String chatJid = friendUsername+"@"+mMainActivity.mServiceName; 260 | Message message = new Message(); 261 | message.addExtension(videoInvitation); 262 | 263 | Chat chat = mMainActivity.createChat(chatJid); 264 | try { 265 | chat.sendMessage(message); 266 | } catch (SmackException.NotConnectedException e) { 267 | e.printStackTrace(); 268 | } 269 | Intent intent = new Intent(mMainActivity,VideoCallActivity.class); 270 | intent.putExtra("remoteName",friendUsername); 271 | mMainActivity.startActivity(intent); 272 | } 273 | }); 274 | 275 | builder.create().show(); 276 | break; 277 | 278 | case "video-deny": 279 | Toast.makeText(mMainActivity,"对方拒绝视频邀请",Toast.LENGTH_SHORT).show(); 280 | break; 281 | //主叫方跳转到VideoCallActivity 282 | case "video-agree": 283 | Intent intent = new Intent(mMainActivity,VideoCallActivity.class); 284 | intent.putExtra("createOffer",true); 285 | Log.e("getFrom",friendUsername); 286 | intent.putExtra("remoteName",friendUsername); 287 | mMainActivity.startActivity(intent); 288 | break; 289 | //视频通话结束,则结束VideoCallActivity。 290 | case "video-ended": 291 | Intent intentEndVideo = new Intent(mMainActivity,VideoCallActivity.class); 292 | intentEndVideo.putExtra("videoEnded",true); 293 | mMainActivity.startActivity(intentEndVideo); 294 | break; 295 | } 296 | } 297 | //如果是SDP信息 298 | else if (message.hasExtension(SDPExtensionElement.ELEMENT_NAME,SDPExtensionElement.NAME_SPACE)) 299 | { 300 | DefaultExtensionElement defaultExtensionElement = 301 | message.getExtension(SDPExtensionElement.ELEMENT_NAME, SDPExtensionElement.NAME_SPACE); 302 | String type = defaultExtensionElement.getValue("type"); 303 | String description = defaultExtensionElement.getValue("description"); 304 | 305 | //发送SDP数据到VideoCallActivity 306 | Intent intent = new Intent(mMainActivity,VideoCallActivity.class); 307 | intent.putExtra("type",type); 308 | intent.putExtra("description",description); 309 | 310 | mMainActivity.startActivity(intent); 311 | } 312 | //如果是ICE Candidate 313 | else if (message.hasExtension(IceCandidateExtensionElement.ELEMENT_NAME, 314 | IceCandidateExtensionElement.NAME_SPACE)) 315 | { 316 | DefaultExtensionElement defaultExtensionElement = 317 | message.getExtension(IceCandidateExtensionElement.ELEMENT_NAME, IceCandidateExtensionElement.NAME_SPACE); 318 | String sdpMid = defaultExtensionElement.getValue("sdpMid"); 319 | int sdpMLineIndex = Integer.parseInt(defaultExtensionElement.getValue("sdpMLineIndex")); 320 | String sdp = defaultExtensionElement.getValue("sdp"); 321 | //发送ICE Candidate数据到VideoCallActivity 322 | Intent intent = new Intent(mMainActivity,VideoCallActivity.class); 323 | intent.putExtra("sdpMid",sdpMid); 324 | intent.putExtra("sdpMLineIndex",sdpMLineIndex); 325 | intent.putExtra("sdp",sdp); 326 | 327 | mMainActivity.startActivity(intent); 328 | } 329 | //如果是聊天文本信息 330 | else if (!mMainActivity.mChatUsers.contains(friendUsername)) 331 | { 332 | Log.e("Handler","handler message"); 333 | String body = message.getBody(); 334 | if (body == null) 335 | { 336 | return; 337 | } 338 | 339 | mMainActivity.mChatUsers.add(friendUsername); 340 | 341 | Intent intent = new Intent(mMainActivity,ChatActivity.class); 342 | intent.putExtra("friendUsername", friendUsername); 343 | intent.putExtra("body",body); 344 | mMainActivity.startActivity(intent); 345 | } 346 | else 347 | { 348 | String body = message.getBody(); 349 | if (body == null) { 350 | return; 351 | } 352 | Intent intent = new Intent(mMainActivity,ChatActivity.class); 353 | intent.putExtra("body",body); 354 | mMainActivity.startActivity(intent); 355 | } 356 | } 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/activity/MultiUserChatRoomActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.activity; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.graphics.Matrix; 8 | import android.os.Handler; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.os.Bundle; 11 | import android.text.SpannableString; 12 | import android.text.Spanned; 13 | import android.text.style.ImageSpan; 14 | import android.util.Log; 15 | import android.view.View; 16 | import android.widget.EditText; 17 | import android.widget.ListView; 18 | import android.widget.Toast; 19 | 20 | import com.example.adapter.ChatListAdapter; 21 | import com.example.common.Const; 22 | import com.example.common.Util; 23 | import com.example.data.ChatData; 24 | import com.example.data.DataWarehouse; 25 | import com.example.data.LoginData; 26 | 27 | import org.jivesoftware.smack.MessageListener; 28 | import org.jivesoftware.smack.chat.ChatManager; 29 | import org.jivesoftware.smack.filter.MessageTypeFilter; 30 | import org.jivesoftware.smack.filter.PacketFilter; 31 | import org.jivesoftware.smack.packet.Message; 32 | import org.jivesoftware.smackx.muc.MultiUserChat; 33 | import org.jivesoftware.smackx.muc.MultiUserChatManager; 34 | 35 | public class MultiUserChatRoomActivity extends ParentActivity implements Const{ 36 | private ChatListAdapter mChatListAdapter; 37 | 38 | private ChatManager mChatManager; 39 | private LoginData mLoginData; 40 | 41 | private EditText mEditTextChatText; 42 | private ListView mListViewChatList; 43 | private String mChatServiceJID; 44 | private String mChatRoomName; 45 | private MultiUserChat mMultiUserChat; 46 | 47 | // private PacketFilter mFilter = new MessageTypeFilter(Message.Type.groupchat); 48 | 49 | @Override 50 | protected void onCreate(Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | setContentView(R.layout.activity_multi_user_chat_room); 53 | 54 | mLoginData = DataWarehouse.getGlobalData(this).loginData; 55 | 56 | mEditTextChatText = (EditText) findViewById(R.id.edittext_chat_text); 57 | mListViewChatList = (ListView) findViewById(R.id.listview_ChatList); 58 | 59 | // mXMPPConnection.addPacketListener(this, mFilter); 60 | mChatServiceJID = getIntent().getStringExtra(JID); 61 | mChatRoomName = getIntent().getStringExtra(CHAT_ROOM_NAME); 62 | 63 | mChatListAdapter = new ChatListAdapter(this); 64 | 65 | mListViewChatList.setAdapter(mChatListAdapter); 66 | mListViewChatList.setDivider(null); 67 | 68 | //加入聊天室 69 | try { 70 | mMultiUserChat = MultiUserChatManager.getInstanceFor(connection). 71 | getMultiUserChat(mChatRoomName+"@"+mChatServiceJID); 72 | mMultiUserChat.join(mLoginData.userName); 73 | } 74 | catch (Exception e) 75 | { 76 | e.printStackTrace(); 77 | Toast.makeText(this, "聊天室加入失败", Toast.LENGTH_LONG).show(); 78 | finish(); 79 | } 80 | 81 | // myHandler = new MyHandler(this); 82 | 83 | //接收群聊信息 84 | mMultiUserChat.addMessageListener(new MessageListener() { 85 | @Override 86 | public void processMessage(Message message) { 87 | Log.e("ChatGroup",message.getFrom()); 88 | Log.e("MsgBody",message.getBody()); 89 | android.os.Message msg = android.os.Message.obtain(); 90 | msg.obj = message; 91 | // myHandler.sendMessage(msg); 92 | handler.sendMessage(msg); 93 | } 94 | }); 95 | } 96 | 97 | private Handler handler = new Handler(){ 98 | @Override 99 | public void handleMessage(android.os.Message msg) { 100 | super.handleMessage(msg); 101 | Message message = (Message) msg.obj; 102 | String body = message.getBody(); 103 | if (body != null) 104 | { 105 | String from = Util.extractUserFromChatGroup(message.getFrom()); 106 | if(mLoginData.userName.equals(from)) 107 | { 108 | return; 109 | } 110 | 111 | ChatData item = new ChatData(); 112 | 113 | item.text = body; 114 | 115 | mChatListAdapter.addItem(item); 116 | mListViewChatList.setSelection(mListViewChatList.getAdapter().getCount() - 1); 117 | } 118 | return; 119 | } 120 | }; 121 | 122 | 123 | 124 | @Override 125 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 126 | switch (resultCode) 127 | { 128 | case 1: 129 | int faceId = data.getIntExtra(KEY_FACE_ID, -1); 130 | if (faceId != -1) 131 | { 132 | String faceResName = FACE_PREFIX + faceId; 133 | 134 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), 135 | Util.getResourceIDFromName(R.drawable.class, faceResName)); 136 | 137 | Matrix matrix = new Matrix(); 138 | matrix.postScale(0.6f, 0.6f); 139 | Bitmap smallBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), 140 | matrix, true); 141 | ImageSpan imageSpan = new ImageSpan(this, smallBitmap); 142 | String faceText = FACE_TEXT_PREFIX + faceId + FACE_TEXT_SUFFIX; 143 | SpannableString spannableString = new SpannableString(faceText); 144 | 145 | spannableString.setSpan(imageSpan, 0, faceText.length(), 146 | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 147 | 148 | mEditTextChatText.getText().insert(mEditTextChatText.getSelectionStart(), 149 | spannableString); 150 | } 151 | break; 152 | } 153 | } 154 | 155 | //发送群聊信息 156 | public void onClick_Send(View view) { 157 | try 158 | { 159 | String text = mEditTextChatText.getText().toString().trim(); 160 | if (!"".equals(text)) 161 | { 162 | mMultiUserChat.sendMessage(text); 163 | mEditTextChatText.setText(""); 164 | 165 | ChatData item = new ChatData(); 166 | item.text = text; 167 | item.user = mLoginData.userName; 168 | item.isOwner = true; 169 | mChatListAdapter.addItem(item); 170 | mListViewChatList.setSelection(mListViewChatList.getAdapter().getCount() - 1); 171 | } 172 | else 173 | { 174 | Toast.makeText(this, "请输入要发送的文本.", Toast.LENGTH_LONG).show(); 175 | } 176 | } 177 | catch (Exception e) 178 | { 179 | e.printStackTrace(); 180 | Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/activity/MultiUserChatRoomsActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.activity; 2 | 3 | import android.content.DialogInterface; 4 | import android.content.Intent; 5 | import android.support.v7.app.AlertDialog; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.os.Bundle; 8 | import android.view.Menu; 9 | import android.view.MenuItem; 10 | import android.view.View; 11 | import android.widget.AdapterView; 12 | import android.widget.EditText; 13 | import android.widget.ListView; 14 | import android.widget.Toast; 15 | 16 | import com.example.adapter.MultiUserChatRoomListAdapter; 17 | import com.example.common.Const; 18 | import com.example.data.DataWarehouse; 19 | 20 | import org.jivesoftware.smack.SmackException; 21 | import org.jivesoftware.smack.XMPPException; 22 | import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 23 | import org.jivesoftware.smackx.disco.packet.DiscoverItems; 24 | import org.jivesoftware.smackx.muc.MultiUserChat; 25 | import org.jivesoftware.smackx.muc.MultiUserChatManager; 26 | import org.jivesoftware.smackx.xdata.Form; 27 | import org.jivesoftware.smackx.xdata.FormField; 28 | 29 | import java.util.List; 30 | 31 | public class MultiUserChatRoomsActivity extends ParentActivity implements Const { 32 | private ListView listView; 33 | private MultiUserChatRoomListAdapter multiUserChatRoomListAdapter; 34 | private ServiceDiscoveryManager serviceDiscoveryManager; 35 | private String chatServiceJID; 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.activity_multi_user_chat_rooms); 41 | 42 | listView = (ListView) findViewById(R.id.listview_chat_rooms); 43 | chatServiceJID = getIntent().getStringExtra(JID); 44 | setTitle(chatServiceJID); 45 | serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); 46 | 47 | try { 48 | multiUserChatRoomListAdapter = new MultiUserChatRoomListAdapter(this, 49 | serviceDiscoveryManager.discoverItems(chatServiceJID).getItems()); 50 | } catch (SmackException.NoResponseException e) { 51 | e.printStackTrace(); 52 | } catch (XMPPException.XMPPErrorException e) { 53 | e.printStackTrace(); 54 | } catch (SmackException.NotConnectedException e) { 55 | e.printStackTrace(); 56 | } 57 | 58 | listView.setAdapter(multiUserChatRoomListAdapter); 59 | 60 | listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 61 | @Override 62 | public void onItemClick(AdapterView parent, View view, int position, long id) { 63 | DiscoverItems.Item item = multiUserChatRoomListAdapter.getChatRoom(position); 64 | Intent intent = new Intent(MultiUserChatRoomsActivity.this, MultiUserChatRoomActivity.class); 65 | intent.putExtra(JID, chatServiceJID); 66 | intent.putExtra(CHAT_ROOM_NAME, item.getName()); 67 | startActivity(intent); 68 | 69 | } 70 | }); 71 | } 72 | 73 | //新建聊天室 74 | public void onClick_New_Room(View view) { 75 | final EditText editText = new EditText(MultiUserChatRoomsActivity.this); 76 | AlertDialog.Builder builder = new AlertDialog.Builder(MultiUserChatRoomsActivity.this) 77 | .setTitle("请输入聊天室名称") 78 | .setView(editText) 79 | .setPositiveButton("确定", new DialogInterface.OnClickListener() { 80 | @Override 81 | public void onClick(DialogInterface dialog, int which) { 82 | String chatRoomName = editText.getText().toString().trim(); 83 | if ("".equals(chatRoomName)) 84 | { 85 | Toast.makeText(MultiUserChatRoomsActivity.this, 86 | "聊天时名称不能为空",Toast.LENGTH_LONG).show(); 87 | return; 88 | } 89 | if (!isConnected()) 90 | { 91 | throw new NullPointerException("服务器连接失败,请先连接服务器"); 92 | } 93 | 94 | // 创建一个MultiUserChat 95 | MultiUserChat muc = null; 96 | muc = MultiUserChatManager.getInstanceFor(connection).getMultiUserChat( 97 | chatRoomName+"@"+chatServiceJID); 98 | // 创建聊天室 99 | try { 100 | boolean isCreate = muc.createOrJoin(DataWarehouse.getGlobalData( 101 | MultiUserChatRoomsActivity.this).loginData.userName); 102 | } catch (XMPPException.XMPPErrorException e) { 103 | e.printStackTrace(); 104 | } catch (SmackException e) { 105 | e.printStackTrace(); 106 | } 107 | Form form = null; 108 | try { 109 | form = muc.getConfigurationForm(); 110 | } catch (SmackException.NoResponseException e) { 111 | e.printStackTrace(); 112 | } catch (XMPPException.XMPPErrorException e) { 113 | e.printStackTrace(); 114 | } catch (SmackException.NotConnectedException e) { 115 | e.printStackTrace(); 116 | } 117 | Form submitForm = form.createAnswerForm(); 118 | List fields = form.getFields(); 119 | for (FormField field : fields) { 120 | if (!FormField.Type.hidden.equals(field.getType()) && 121 | field.getVariable() != null) { 122 | submitForm.setDefaultAnswer(field.getVariable()); 123 | } 124 | } 125 | 126 | // 设置聊天室为公共聊天室 127 | submitForm.setAnswer("muc#roomconfig_publicroom", true); 128 | // 设置聊天室是永久存在的 129 | submitForm.setAnswer("muc#roomconfig_persistentroom", true); 130 | 131 | try { 132 | muc.sendConfigurationForm(submitForm); 133 | } catch (SmackException.NoResponseException e) { 134 | e.printStackTrace(); 135 | } catch (XMPPException.XMPPErrorException e) { 136 | e.printStackTrace(); 137 | } catch (SmackException.NotConnectedException e) { 138 | e.printStackTrace(); 139 | } 140 | try { 141 | multiUserChatRoomListAdapter.updateChatRoom(serviceDiscoveryManager. 142 | discoverItems(chatServiceJID).getItems()); 143 | } catch (SmackException.NoResponseException e) { 144 | e.printStackTrace(); 145 | } catch (XMPPException.XMPPErrorException e) { 146 | e.printStackTrace(); 147 | } catch (SmackException.NotConnectedException e) { 148 | e.printStackTrace(); 149 | } 150 | Toast.makeText(MultiUserChatRoomsActivity.this, "成功创建聊天室", Toast.LENGTH_LONG).show(); 151 | 152 | } 153 | }) 154 | .setNegativeButton("取消",null); 155 | builder.create().show(); 156 | } 157 | 158 | @Override 159 | public boolean onCreateOptionsMenu(Menu menu) { 160 | // Inflate the menu; this adds items to the action bar if it is present. 161 | getMenuInflater().inflate(R.menu.menu_chat_room, menu); 162 | return true; 163 | } 164 | 165 | @Override 166 | public boolean onOptionsItemSelected(MenuItem item) { 167 | int id = item.getItemId(); 168 | switch(id) 169 | { 170 | case R.id.menu_item_chat_room_refresh: 171 | try 172 | { 173 | multiUserChatRoomListAdapter.updateChatRoom(serviceDiscoveryManager. 174 | discoverItems(chatServiceJID).getItems()); 175 | Toast.makeText(this, "成功刷新", Toast.LENGTH_LONG).show(); 176 | } 177 | catch (Exception e) 178 | { 179 | Toast.makeText(this, "刷新失败", Toast.LENGTH_LONG).show(); 180 | } 181 | break; 182 | } 183 | 184 | return super.onOptionsItemSelected(item); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/activity/ParentActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.activity; 2 | /*创建ParentActivity的原因:是因为好多Activity都需要用到下面的connetiton 3 | *对象,故将需要connection的Activity继承ParentActivity对象即可。 4 | */ 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.os.Bundle; 7 | 8 | import com.example.data.DataWarehouse; 9 | 10 | import org.jivesoftware.smack.SmackException; 11 | import org.jivesoftware.smack.XMPPException; 12 | import org.jivesoftware.smack.chat.Chat; 13 | import org.jivesoftware.smack.chat.ChatManager; 14 | import org.jivesoftware.smack.tcp.XMPPTCPConnection; 15 | 16 | import java.io.IOException; 17 | 18 | public class ParentActivity extends AppCompatActivity { 19 | protected XMPPTCPConnection connection; 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | connection = DataWarehouse.getXMPPTCPConnection(this); 25 | } 26 | 27 | protected boolean isConnected() 28 | { 29 | if (connection == null) 30 | { 31 | return false; 32 | } 33 | if (!connection.isConnected()) 34 | { 35 | try { 36 | connection.connect(); 37 | return true; 38 | } catch (SmackException e) { 39 | e.printStackTrace(); 40 | } catch (IOException e) { 41 | e.printStackTrace(); 42 | } catch (XMPPException e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | return true; 47 | } 48 | 49 | //获取聊天对象管理器 50 | public ChatManager getChatManager() 51 | { 52 | if (isConnected()) 53 | { 54 | ChatManager chatManager = ChatManager.getInstanceFor(connection); 55 | return chatManager; 56 | } 57 | throw new NullPointerException("服务器连接失败,请先连接服务器!"); 58 | } 59 | 60 | //chatJid: friendUsername@serviceName 61 | protected Chat createChat(String chatJid) 62 | { 63 | if (isConnected()) 64 | { 65 | ChatManager chatManager = ChatManager.getInstanceFor(connection); 66 | return chatManager.createChat(chatJid); 67 | } 68 | throw new NullPointerException("连接服务器失败,请先连接服务器!"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/activity/RegisterActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.activity; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.EditText; 9 | import android.widget.Toast; 10 | 11 | import com.example.common.XMPPUtil; 12 | 13 | import org.jivesoftware.smack.SmackException; 14 | import org.jivesoftware.smack.XMPPConnection; 15 | import org.jivesoftware.smack.XMPPException; 16 | import org.jivesoftware.smack.packet.IQ; 17 | import org.jivesoftware.smack.tcp.XMPPTCPConnection; 18 | import org.jivesoftware.smackx.iqregister.AccountManager; 19 | import org.jivesoftware.smackx.iqregister.packet.Registration; 20 | 21 | import java.io.IOException; 22 | 23 | public class RegisterActivity extends AppCompatActivity { 24 | private EditText mEditTextUsername; 25 | private EditText mEditTextPassword; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.activity_register); 31 | 32 | mEditTextUsername = (EditText) findViewById(R.id.userRegister); 33 | mEditTextPassword = (EditText) findViewById(R.id.passwordInput); 34 | } 35 | 36 | 37 | public void onClickRegister(View view) { 38 | new Thread(new Runnable() { 39 | @Override 40 | public void run() { 41 | registerUser(mEditTextUsername.getText().toString(),mEditTextPassword.getText().toString()); 42 | } 43 | }).start(); 44 | } 45 | 46 | 47 | //注册用户 48 | public Boolean registerUser(String username,String password) 49 | { 50 | try { 51 | XMPPTCPConnection connection = XMPPUtil.getXMPPConnection(this); 52 | if (connection == null) 53 | { 54 | try { 55 | connection.connect(); 56 | } catch (SmackException e) { 57 | e.printStackTrace(); 58 | } catch (IOException e) { 59 | e.printStackTrace(); 60 | } catch (XMPPException e) { 61 | e.printStackTrace(); 62 | } 63 | Log.e("connect","连接服务器失败!"); 64 | } 65 | 66 | AccountManager.getInstance(connection).createAccount(username,password); 67 | finish(); 68 | return true; 69 | } 70 | catch (SmackException.NoResponseException | XMPPException.XMPPErrorException | 71 | SmackException.NotConnectedException e) 72 | { 73 | Log.e("register", "注册失败!"); 74 | return false; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/activity/VideoCallActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.activity; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.media.AudioManager; 6 | import android.opengl.GLSurfaceView; 7 | import android.os.Bundle; 8 | import android.util.DisplayMetrics; 9 | import android.util.Log; 10 | import android.view.WindowManager; 11 | 12 | import com.example.CustomXML.IceCandidateExtensionElement; 13 | import com.example.CustomXML.SDPExtensionElement; 14 | import com.example.CustomXML.VideoInvitation; 15 | 16 | import org.jivesoftware.smack.SmackException; 17 | import org.jivesoftware.smack.chat.Chat; 18 | import org.jivesoftware.smack.packet.Message; 19 | import org.webrtc.AudioSource; 20 | import org.webrtc.AudioTrack; 21 | import org.webrtc.DataChannel; 22 | import org.webrtc.IceCandidate; 23 | import org.webrtc.MediaConstraints; 24 | import org.webrtc.MediaStream; 25 | import org.webrtc.PeerConnection; 26 | import org.webrtc.PeerConnectionFactory; 27 | import org.webrtc.SdpObserver; 28 | import org.webrtc.SessionDescription; 29 | import org.webrtc.VideoCapturer; 30 | import org.webrtc.VideoCapturerAndroid; 31 | import org.webrtc.VideoRenderer; 32 | import org.webrtc.VideoRendererGui; 33 | import org.webrtc.VideoSource; 34 | import org.webrtc.VideoTrack; 35 | 36 | import java.util.ArrayList; 37 | import java.util.LinkedList; 38 | import java.util.List; 39 | 40 | public class VideoCallActivity extends ParentActivity{ 41 | public static final String VIDEO_TRACK_ID = "ARDAMSv0"; 42 | public static final String AUDIO_TRACK_ID = "ARDAMSa0"; 43 | public static final String LOCAL_MEDIA_STREAM_ID = "ARDAMS"; 44 | private String mServiceName; //XMPP服务器名称 45 | 46 | private GLSurfaceView mGLSurfaceView; 47 | // private GLSurfaceView mGLSurfaceViewRemote; 48 | 49 | private PeerConnection pc; 50 | private final PCObserver pcObserver = new PCObserver(); 51 | private final SDPObserver sdpObserver = new SDPObserver(); 52 | 53 | private MediaConstraints sdpMediaConstraints; 54 | private MediaConstraints pcConstraints; 55 | private String remoteName; 56 | IceCandidate remoteIceCandidate; 57 | 58 | private boolean mIsInited; 59 | private boolean mIsCalled; 60 | PeerConnectionFactory factory; 61 | VideoCapturer videoCapturer; 62 | VideoSource videoSource; 63 | 64 | VideoRenderer localVideoRenderer; 65 | VideoRenderer remoteVideoRenderer; 66 | 67 | AudioManager audioManager; 68 | 69 | @Override 70 | protected void onCreate(Bundle savedInstanceState) { 71 | super.onCreate(savedInstanceState); 72 | setContentView(R.layout.activity_video_call); 73 | 74 | //打开扬声器 75 | audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); 76 | audioManager.setSpeakerphoneOn(true); 77 | 78 | mServiceName = connection.getServiceName(); 79 | mGLSurfaceView = (GLSurfaceView) findViewById(R.id.glsurfaceview); 80 | // mGLSurfaceViewRemote = (GLSurfaceView) findViewById(R.id.glsurfaceview_remote); 81 | //检查初始化音视频设备是否成功 82 | if (!PeerConnectionFactory.initializeAndroidGlobals(this,true,true,true,null)) 83 | { 84 | Log.e("init","PeerConnectionFactory init fail!"); 85 | return; 86 | } 87 | 88 | // Intent intent = getIntent().getBundleExtra() 89 | 90 | //Media条件信息SDP接口 91 | sdpMediaConstraints = new MediaConstraints(); 92 | //接受远程音频 93 | sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair( 94 | "OfferToReceiveAudio", "true")); 95 | //接受远程视频 96 | sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair( 97 | "OfferToReceiveVideo", "true")); 98 | // sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation","true")); 99 | // sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl","true")); 100 | // sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseReduction","true")); 101 | 102 | factory = new PeerConnectionFactory(); 103 | 104 | //iceServer List对象获取 105 | List iceServers = new ArrayList<>(); 106 | pcConstraints = new MediaConstraints(); 107 | pcConstraints.optional.add(new MediaConstraints.KeyValuePair( 108 | "DtlsSrtpKeyAgreement", "true")); 109 | pcConstraints.mandatory.add(new 110 | MediaConstraints.KeyValuePair("VoiceActivityDetection", "false")); 111 | // pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation","true")); 112 | // pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl","true")); 113 | // pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseReduction","true")); 114 | 115 | pc = factory.createPeerConnection(iceServers,pcConstraints,pcObserver); 116 | 117 | 118 | mIsInited = false; 119 | mIsCalled=false; 120 | 121 | boolean offer=getIntent().getBooleanExtra("createOffer",false); 122 | //offer:如果offer为true表示主叫方初始化,如果为false表示被叫方初始化。 123 | remoteName = getIntent().getStringExtra("remoteName"); 124 | if(!offer) 125 | { 126 | initialSystem(); 127 | } 128 | else { 129 | callRemote(remoteName); 130 | } 131 | //当VideoActivity已经打开时,处理后续的intent传过来的数据。 132 | processExtraData(); 133 | } 134 | 135 | @Override 136 | protected void onNewIntent(Intent intent) { 137 | super.onNewIntent(intent); 138 | setIntent(intent); 139 | processExtraData(); 140 | } 141 | 142 | @Override 143 | protected void onDestroy() { 144 | super.onDestroy(); 145 | //通话结束,发送通话结束消息 146 | videoCallEnded(); 147 | //释放资源 148 | videoCapturer.dispose(); 149 | videoSource.stop(); 150 | if (pc != null) { 151 | pc.dispose(); 152 | pc = null; 153 | } 154 | 155 | audioManager.setSpeakerphoneOn(false); 156 | } 157 | 158 | private void videoCallEnded() { 159 | String chatJid = remoteName+"@"+mServiceName; 160 | Message message = new Message(); 161 | VideoInvitation videoInvitation = new VideoInvitation(); 162 | videoInvitation.setTypeText("video-ended"); 163 | message.addExtension(videoInvitation); 164 | Chat chat = createChat(chatJid); 165 | try { 166 | chat.sendMessage(message); 167 | } catch (SmackException.NotConnectedException e) { 168 | e.printStackTrace(); 169 | } 170 | } 171 | 172 | private void processExtraData() { 173 | Intent intent = getIntent(); 174 | //获取SDP数据 175 | String sdpType = intent.getStringExtra("type"); 176 | String sdpDescription = intent.getStringExtra("description"); 177 | if (sdpType != null) 178 | { 179 | 180 | SessionDescription.Type type = SessionDescription.Type.fromCanonicalForm(sdpType); 181 | SessionDescription sdp = new SessionDescription(type,sdpDescription); 182 | if (pc == null) 183 | { 184 | Log.e("pc","pc == null"); 185 | } 186 | pc.setRemoteDescription(sdpObserver,sdp); 187 | 188 | //如果是offer,则被叫方createAnswer 189 | if (sdpType.equals("offer")) 190 | { 191 | mIsCalled = true; 192 | pc.createAnswer(sdpObserver,sdpMediaConstraints); 193 | } 194 | 195 | } 196 | 197 | //获取ICE Candidate数据 198 | String iceSdpMid = intent.getStringExtra("sdpMid"); 199 | int iceSdpMLineIndex = intent.getIntExtra("sdpMLineIndex",-1); 200 | String iceSdp = intent.getStringExtra("sdp"); 201 | if (iceSdpMid != null) 202 | { 203 | IceCandidate iceCandidate = new IceCandidate(iceSdpMid,iceSdpMLineIndex,iceSdp); 204 | 205 | if (remoteIceCandidate == null) 206 | { 207 | remoteIceCandidate = iceCandidate; 208 | } 209 | 210 | //下面这步放到函数drainRemoteCandidates()中 211 | /*//添加远端的IceCandidate到pc 212 | pc.addIceCandidate(iceCandidate);*/ 213 | } 214 | 215 | 216 | //结束activity 217 | boolean videoEnded = intent.getBooleanExtra("videoEnded",false); 218 | if (videoEnded) 219 | { 220 | finish(); 221 | } 222 | } 223 | 224 | /* //chatJid: friendUsername@serviceName 225 | private Chat createChat(String chatJid) 226 | { 227 | if (isConnected()) 228 | { 229 | ChatManager chatManager = ChatManager.getInstanceFor(connection); 230 | return chatManager.createChat(chatJid); 231 | } 232 | throw new NullPointerException("连接服务器失败,请先连接服务器!"); 233 | }*/ 234 | 235 | private void callRemote(String remoteName) { 236 | initialSystem(); 237 | //createOffer 238 | pc.createOffer(sdpObserver,sdpMediaConstraints); 239 | } 240 | 241 | private void initialSystem() { 242 | if (mIsInited) 243 | { 244 | return; 245 | } 246 | //获取前置摄像头本地视频流 247 | String frontDeviceName = VideoCapturerAndroid.getNameOfFrontFacingDevice(); 248 | // String frontDeviceName = "Camera 1, Facing front, Orientation 0"; 249 | Log.e("CameraName","CameraName: "+frontDeviceName); 250 | videoCapturer = VideoCapturerAndroid.create(frontDeviceName); 251 | 252 | /* //获取后置摄像头本地视频流 253 | String backDeviceName = VideoCapturerAndroid.getNameOfBackFacingDevice(); 254 | Log.e("CameraName","CameraName: "+backDeviceName); 255 | videoCapturer = VideoCapturerAndroid.create(backDeviceName);*/ 256 | 257 | /*int cameraNums = VideoCapturerAndroid.getDeviceCount(); 258 | Log.e("CameraCount","CameraCount: "+cameraNums);*/ 259 | 260 | /* //手机上直接使用上面注释代码指定前置和后置摄像头就行,当外接USB摄像头时, 261 | //使用下面代码获取USB摄像头。 262 | String[] cameraNames = VideoCapturerAndroid.getDeviceNames(); 263 | for (String cameraName : cameraNames) 264 | { 265 | videoCapturer = VideoCapturerAndroid.create(cameraName); 266 | if (videoCapturer != null) 267 | { 268 | break; 269 | } 270 | }*/ 271 | 272 | if (videoCapturer == null) 273 | { 274 | Log.e("open","fail to open camera"); 275 | return; 276 | } 277 | //视频 278 | MediaConstraints mediaConstraints = new MediaConstraints(); 279 | videoSource = factory.createVideoSource(videoCapturer, mediaConstraints); 280 | VideoTrack localVideoTrack = factory.createVideoTrack(VIDEO_TRACK_ID, videoSource); 281 | 282 | //音频 283 | MediaConstraints audioConstraints = new MediaConstraints(); 284 | AudioSource audioSource = factory.createAudioSource(audioConstraints); 285 | AudioTrack localAudioTrack = factory.createAudioTrack(AUDIO_TRACK_ID,audioSource); 286 | 287 | Runnable runnable = new Runnable() { 288 | @Override 289 | public void run() { 290 | 291 | } 292 | }; 293 | VideoRendererGui.setView(mGLSurfaceView,runnable); 294 | 295 | WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE); 296 | DisplayMetrics dm = new DisplayMetrics(); 297 | wm.getDefaultDisplay().getMetrics(dm); 298 | int width = dm.widthPixels; // 屏幕宽度(像素) 299 | int height = dm.heightPixels; // 屏幕高度(像素) 300 | 301 | try { 302 | //改成ScalingType.SCALE_ASPECT_FILL可以显示双方视频,但是显示比例不美观,并且不知道最后一个参数true和false的含义。 303 | localVideoRenderer = VideoRendererGui.createGui(0,0,100,100, VideoRendererGui.ScalingType.SCALE_ASPECT_FILL,true); 304 | remoteVideoRenderer = VideoRendererGui.createGui(0,0,100,100, VideoRendererGui.ScalingType.SCALE_ASPECT_FILL,true); 305 | localVideoTrack.addRenderer(localVideoRenderer); 306 | } catch (Exception e) { 307 | e.printStackTrace(); 308 | } 309 | 310 | MediaStream localMediaStream = factory.createLocalMediaStream(LOCAL_MEDIA_STREAM_ID); 311 | 312 | localMediaStream.addTrack(localAudioTrack); 313 | localMediaStream.addTrack(localVideoTrack); 314 | 315 | pc.addStream(localMediaStream); 316 | } 317 | 318 | private List getIceServers(String url,String user,String credential) 319 | { 320 | PeerConnection.IceServer turn = new PeerConnection.IceServer( 321 | url,user,credential); 322 | LinkedList iceServers = new LinkedList(); 323 | iceServers.add(turn); 324 | return iceServers; 325 | } 326 | 327 | private class PCObserver implements PeerConnection.Observer 328 | { 329 | 330 | @Override 331 | public void onSignalingChange(PeerConnection.SignalingState signalingState) { 332 | 333 | } 334 | 335 | @Override 336 | public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) { 337 | 338 | } 339 | 340 | @Override 341 | public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) { 342 | 343 | } 344 | 345 | //发送ICE候选到其他客户端 346 | @Override 347 | public void onIceCandidate(final IceCandidate iceCandidate) { 348 | //利用XMPP发送iceCandidate到其他客户端 349 | runOnUiThread(new Runnable() { 350 | @Override 351 | public void run() { 352 | String chatJid = remoteName+"@"+mServiceName; 353 | Message message = new Message(); 354 | IceCandidateExtensionElement iceCandidateExtensionElement= 355 | new IceCandidateExtensionElement(); 356 | iceCandidateExtensionElement.setSdpMidText(iceCandidate.sdpMid); 357 | iceCandidateExtensionElement.setSdpMLineIndexText(iceCandidate.sdpMLineIndex); 358 | iceCandidateExtensionElement.setSdpText(iceCandidate.sdp); 359 | message.addExtension(iceCandidateExtensionElement); 360 | Chat chat = createChat(chatJid); 361 | try { 362 | chat.sendMessage(message); 363 | } catch (SmackException.NotConnectedException e) { 364 | e.printStackTrace(); 365 | } 366 | 367 | } 368 | }); 369 | } 370 | 371 | //Display a media stream from remote 372 | @Override 373 | public void onAddStream(final MediaStream mediaStream) { 374 | runOnUiThread(new Runnable() { 375 | @Override 376 | public void run() { 377 | if (pc == null) 378 | { 379 | Log.e("onAddStream","pc == null"); 380 | return; 381 | } 382 | if (mediaStream.videoTracks.size()>1 || mediaStream.audioTracks.size()>1) 383 | { 384 | Log.e("onAddStream","size > 1"); 385 | return; 386 | } 387 | if (mediaStream.videoTracks.size() == 1) 388 | { 389 | /* Log.e("addStream","onAddStream() onStart"); 390 | Log.e("mediaStream","mediaStream: "+mediaStream.toString()); 391 | Log.e("streamSize","streamSize: "+mediaStream.videoTracks.size());*/ 392 | VideoTrack videoTrack = mediaStream.videoTracks.get(0); 393 | videoTrack.addRenderer(remoteVideoRenderer); 394 | } 395 | } 396 | }); 397 | } 398 | 399 | @Override 400 | public void onRemoveStream(final MediaStream mediaStream) { 401 | runOnUiThread(new Runnable() { 402 | @Override 403 | public void run() { 404 | mediaStream.videoTracks.get(0).dispose(); 405 | } 406 | }); 407 | } 408 | 409 | @Override 410 | public void onDataChannel(DataChannel dataChannel) { 411 | 412 | } 413 | 414 | @Override 415 | public void onRenegotiationNeeded() { 416 | 417 | } 418 | } 419 | 420 | private class SDPObserver implements SdpObserver 421 | { 422 | 423 | @Override 424 | public void onCreateSuccess(final SessionDescription sessionDescription) { 425 | //sendMessage(offer); 426 | runOnUiThread(new Runnable() { 427 | @Override 428 | public void run() { 429 | String chatJid = remoteName+"@"+mServiceName; 430 | Message message = new Message(); 431 | SDPExtensionElement sdpExtensionElement = new SDPExtensionElement(); 432 | sdpExtensionElement.setTypeText(sessionDescription.type.canonicalForm()); 433 | sdpExtensionElement.setDescriptionText(sessionDescription.description); 434 | message.addExtension(sdpExtensionElement); 435 | Chat chat = createChat(chatJid); 436 | try { 437 | chat.sendMessage(message); 438 | } catch (SmackException.NotConnectedException e) { 439 | e.printStackTrace(); 440 | } 441 | 442 | pc.setLocalDescription(sdpObserver,sessionDescription); 443 | } 444 | }); 445 | } 446 | 447 | @Override 448 | public void onSetSuccess() { 449 | runOnUiThread(new Runnable() { 450 | @Override 451 | public void run() { 452 | //主叫方 453 | if (!mIsCalled) 454 | { 455 | if (pc.getRemoteDescription() != null) 456 | { 457 | drainRemoteCandidates(); 458 | } 459 | } 460 | //被叫方 461 | else 462 | { 463 | //如果被叫方还没有createAnswer 464 | if (pc.getLocalDescription() == null) 465 | { 466 | Log.e("SDPObserver", "SDPObserver create answer"); 467 | } 468 | else 469 | { 470 | drainRemoteCandidates(); 471 | } 472 | } 473 | } 474 | }); 475 | } 476 | 477 | private void drainRemoteCandidates() { 478 | if (remoteIceCandidate == null) 479 | { 480 | Log.e("SDPObserver","remoteIceCandidate == null"); 481 | return; 482 | } 483 | pc.addIceCandidate(remoteIceCandidate); 484 | Log.e("IceCanditate","添加IceCandidate成功"); 485 | remoteIceCandidate = null; 486 | } 487 | 488 | @Override 489 | public void onCreateFailure(String s) { 490 | Log.e("SDPObserver","onCreateFailure"); 491 | } 492 | 493 | @Override 494 | public void onSetFailure(String s) { 495 | Log.e("SDPObserver","onSetFailure"); 496 | } 497 | } 498 | } 499 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/adapter/ChatListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.adapter; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.BaseAdapter; 8 | import android.widget.ImageView; 9 | import android.widget.TextView; 10 | 11 | import com.example.activity.R; 12 | import com.example.common.Util; 13 | import com.example.data.ChatData; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | 19 | public class ChatListAdapter extends BaseAdapter { 20 | private Context mContext; 21 | private List mChatDataList; 22 | private LayoutInflater mLayoutInflater; 23 | 24 | public ChatListAdapter(Context mContext) { 25 | this.mContext = mContext; 26 | mChatDataList = new ArrayList<>(); 27 | mLayoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 28 | } 29 | 30 | @Override 31 | public int getCount() { 32 | return mChatDataList.size(); 33 | } 34 | 35 | @Override 36 | public Object getItem(int position) { 37 | return position mChatServiceList; 19 | 20 | public ChatServiceListAdapter(Context mContext, List chatServiceList) { 21 | super(mContext); 22 | mChatServiceList = new ArrayList<>(); 23 | for (int i=0; i mUsers; 37 | private Map mUserMap; 38 | private Context mContext; 39 | private LayoutInflater mLayoutInflater; 40 | 41 | private XMPPTCPConnection connection; 42 | private String mServiceName; 43 | 44 | public FriendListAdapter(Context mContext,Collection entries) { 45 | this.mContext = mContext; 46 | mLayoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 47 | mUsers = new ArrayList<>(); 48 | mUserMap = new HashMap<>() ; 49 | connection = DataWarehouse.getXMPPTCPConnection(mContext); 50 | mServiceName = connection.getServiceName(); 51 | if (entries != null) 52 | { 53 | Iterator iterator = entries.iterator(); 54 | while (iterator.hasNext()) 55 | { 56 | RosterEntry entry = iterator.next(); 57 | //有的返回的用户JID后面包含@ 58 | if (entry.getUser().indexOf("@") == -1) 59 | { 60 | UserData userData = new UserData(entry.getUser()); 61 | mUserMap.put(entry.getUser(),entry.getName()); 62 | mUsers.add(userData); 63 | } 64 | } 65 | } 66 | } 67 | 68 | @Override 69 | public int getCount() { 70 | return mUsers.size(); 71 | } 72 | 73 | @Override 74 | public Object getItem(int position) { 75 | return mUsers.get(position); 76 | } 77 | 78 | @Override 79 | public long getItemId(int position) { 80 | return position; 81 | } 82 | 83 | //根据位置获取用户名 84 | public String getUsername(int position) 85 | { 86 | return mUsers.get(position).username; 87 | } 88 | 89 | 90 | //实现ListView的实时刷新。 91 | public void addUserData(UserData userData) 92 | { 93 | mUsers.add(userData); 94 | //如果adapter有数据更细,调用此函数可以更细adapter 95 | notifyDataSetChanged(); 96 | } 97 | 98 | public void removeUserData(int position) 99 | { 100 | XMPPTCPConnection connection = DataWarehouse.getXMPPTCPConnection(mContext); 101 | RosterEntry entry = Roster.getInstanceFor(connection).getEntry(getUsername(position)); 102 | if (entry != null) 103 | { 104 | try { 105 | Roster.getInstanceFor(connection).removeEntry(entry); 106 | }catch (Exception e) 107 | { 108 | e.printStackTrace(); 109 | } 110 | mUsers.remove(position); 111 | notifyDataSetChanged(); 112 | } 113 | } 114 | 115 | @Override 116 | public View getView(final int position, View convertView, ViewGroup parent) { 117 | if (convertView == null) 118 | { 119 | convertView = mLayoutInflater.inflate(R.layout.friend_list_item,null); 120 | } 121 | TextView user = (TextView) convertView.findViewById(R.id.textview_friend_list_item_name); 122 | user.setText(getUsername(position)); 123 | 124 | 125 | 126 | return convertView; 127 | } 128 | 129 | //chatJid: friendUsername@serviceName 130 | private Chat createChat(String chatJid) 131 | { 132 | if (isConnected()) 133 | { 134 | ChatManager chatManager = ChatManager.getInstanceFor(connection); 135 | return chatManager.createChat(chatJid); 136 | } 137 | throw new NullPointerException("连接服务器失败,请先连接服务器!"); 138 | } 139 | 140 | private boolean isConnected() 141 | { 142 | if (connection == null) 143 | { 144 | return false; 145 | } 146 | if (!connection.isConnected()) 147 | { 148 | try { 149 | connection.connect(); 150 | return true; 151 | } catch (SmackException e) { 152 | e.printStackTrace(); 153 | } catch (IOException e) { 154 | e.printStackTrace(); 155 | } catch (XMPPException e) { 156 | e.printStackTrace(); 157 | } 158 | } 159 | return true; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/adapter/MultiUserChatRoomListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.adapter; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.TextView; 7 | 8 | import com.example.activity.R; 9 | import com.example.data.ChatRoomData; 10 | 11 | import org.jivesoftware.smackx.disco.packet.DiscoverItems; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | 17 | public class MultiUserChatRoomListAdapter extends ParentAdapter { 18 | private List mChatRoomList; 19 | 20 | public MultiUserChatRoomListAdapter(Context mContext, List chatRoomList) { 21 | super(mContext); 22 | mChatRoomList = new ArrayList<>(); 23 | for (int i=0; i chatRoomList) 53 | { 54 | mChatRoomList.clear(); 55 | for (int i=0; i 17 | */ 18 | String FACE_TEXT_SUFFIX = ":>"; 19 | 20 | String JID = "jid"; 21 | 22 | String CHAT_ROOM_NAME = "chat_room_name"; // 聊天室名称 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/common/Storage.java: -------------------------------------------------------------------------------- 1 | package com.example.common; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | 7 | public class Storage { 8 | private final static String STORAGE_FINAL_NAME = "chatclent.config"; 9 | 10 | //获取SharedPreferences 11 | private static SharedPreferences getSharedPreference(Context ctx) 12 | { 13 | return ctx.getSharedPreferences(STORAGE_FINAL_NAME,Context.MODE_PRIVATE); 14 | } 15 | 16 | //往SharedPreferences中存放String类型的值。 17 | public static void putString(Context ctx,String key,String value) 18 | { 19 | SharedPreferences sharedPreferences = getSharedPreference(ctx); 20 | sharedPreferences.edit().putString(key,value).commit(); 21 | } 22 | 23 | //获取键值时,可以设置默认值,也可以不设置默认值。故将默认值设置为String...可变参数。 24 | public static String getString(Context ctx,String key,String... defaultValue) 25 | { 26 | SharedPreferences sharedPreferences = getSharedPreference(ctx); 27 | //如果getString()不设置默认值,则将默认值设置为""。 28 | String dv = ""; 29 | //如果getString()设置了默认值,则读取defaultValue中的第一个值。 30 | for(String v : defaultValue) 31 | { 32 | dv = v; 33 | break; 34 | } 35 | return sharedPreferences.getString(key,dv); 36 | } 37 | 38 | //往SharedPreferences中存放Boolean类型的数据。 39 | public static void putBollean(Context ctx,String key,Boolean value) 40 | { 41 | SharedPreferences sharedPreferences = getSharedPreference(ctx); 42 | sharedPreferences.edit().putBoolean(key,value).commit(); 43 | } 44 | 45 | public static Boolean getBoolean(Context ctx,String key,Boolean... defaultValue) 46 | { 47 | SharedPreferences sharedPreferences = getSharedPreference(ctx); 48 | boolean dv = false; 49 | for (boolean v : defaultValue) 50 | { 51 | dv = v; 52 | break; 53 | } 54 | return sharedPreferences.getBoolean(key,dv); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/common/Util.java: -------------------------------------------------------------------------------- 1 | package com.example.common; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Matrix; 7 | import android.text.SpannableString; 8 | import android.text.Spanned; 9 | import android.text.style.ImageSpan; 10 | import android.widget.TextView; 11 | 12 | import com.example.activity.R; 13 | 14 | import java.lang.reflect.Field; 15 | import java.util.regex.Matcher; 16 | import java.util.regex.Pattern; 17 | 18 | 19 | public class Util { 20 | // 从包含服务名的user中(user2@thor)中提取@之前的内容 21 | public static String extractUserFromChat(String user) 22 | { 23 | int index = user.indexOf("@"); 24 | if(index == -1) 25 | { 26 | return user; 27 | } 28 | else 29 | { 30 | return user.substring(0, index); 31 | } 32 | } 33 | 34 | //根据资源名获取资源ID 35 | public static int getResourceIDFromName(Class c,String name) 36 | { 37 | int resID = -1; 38 | try { 39 | //利用Java反射 40 | Field field = c.getField(name); 41 | resID = field.getInt(null); 42 | } 43 | catch (Exception e) 44 | { 45 | e.printStackTrace(); 46 | } 47 | 48 | return resID; 49 | } 50 | 51 | public static void updateFacesForTextView(Context context, TextView textView) 52 | { 53 | // ab<:12:>xyz<:1:>abc 54 | String regExp = Const.FACE_TEXT_PREFIX + "\\d+" + Const.FACE_TEXT_SUFFIX; 55 | 56 | Pattern pattern = Pattern.compile(regExp); 57 | 58 | String oldText = textView.getText().toString(); 59 | 60 | textView.setText(""); 61 | String oldTextArray[] = oldText.split(regExp); 62 | Matcher matcher = pattern.matcher(oldText); 63 | int start = 0; 64 | int i = 0; 65 | int count = 0; 66 | 67 | while(matcher.find(start)) 68 | { 69 | if(i < oldTextArray.length) 70 | { 71 | textView.append(oldTextArray[i++]); 72 | } 73 | count++; 74 | String faceText = matcher.group(); 75 | int faceTextSuffixStartIndex = faceText.indexOf(Const.FACE_TEXT_SUFFIX); 76 | int faceId = Integer.parseInt(faceText.substring(Const.FACE_TEXT_PREFIX.length(), 77 | faceTextSuffixStartIndex)); 78 | start = matcher.end(); 79 | String faceResName = Const.FACE_PREFIX + faceId; 80 | int resId = Util.getResourceIDFromName(R.drawable.class, faceResName); 81 | 82 | if(resId == -1) 83 | { 84 | textView.append(faceText); 85 | continue; 86 | } 87 | 88 | Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), 89 | resId); 90 | Matrix matrix = new Matrix(); 91 | matrix.postScale(0.6f, 0.6f); 92 | Bitmap smallBitmap = Bitmap.createBitmap(bitmap, 0,0, bitmap.getWidth(), 93 | bitmap.getHeight(),matrix,true); 94 | 95 | ImageSpan imageSpan = new ImageSpan(context, smallBitmap); 96 | SpannableString spannableString = new SpannableString(faceText); 97 | spannableString.setSpan(imageSpan, 0, faceText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 98 | 99 | textView.append(spannableString); 100 | 101 | } 102 | 103 | if(count == oldTextArray.length - 1) 104 | { 105 | textView.append(oldTextArray[count]); 106 | } 107 | } 108 | 109 | public static String extractUserFromChatGroup(String user) 110 | { 111 | int index = user.indexOf("@"); 112 | if (index == -1) 113 | { 114 | return user; 115 | } 116 | else 117 | { 118 | int index1 = user.indexOf("/"); 119 | if (index1 == -1) 120 | return user; 121 | else 122 | return user.substring(index1 + 1); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/common/XMPPUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.common; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | import com.example.data.DataWarehouse; 6 | import org.jivesoftware.smack.ConnectionConfiguration; 7 | import org.jivesoftware.smack.ConnectionListener; 8 | import org.jivesoftware.smack.SmackException; 9 | import org.jivesoftware.smack.XMPPConnection; 10 | import org.jivesoftware.smack.XMPPException; 11 | import org.jivesoftware.smack.tcp.XMPPTCPConnection; 12 | import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; 13 | import java.io.IOException; 14 | 15 | 16 | 17 | public class XMPPUtil { 18 | 19 | private static String host = "192.168.0.5"; 20 | private static int port = 5222; 21 | private static String serviceName = "192.168.0.5"; 22 | 23 | 24 | 25 | //连接服务器 26 | public static XMPPTCPConnection getXMPPConnection(Context ctx) { 27 | 28 | // SmackConfiguration.DEBUG = true; 29 | XMPPTCPConnectionConfiguration.Builder configBuilder = XMPPTCPConnectionConfiguration.builder(); 30 | //设置服务器IP地址 31 | configBuilder.setHost(host); 32 | //设置服务器端口 33 | configBuilder.setPort(port); 34 | //设置服务器名称 35 | configBuilder.setServiceName(serviceName); 36 | //设置开启调试 37 | configBuilder.setDebuggerEnabled(true); 38 | //设置开启压缩,可以节省流量 39 | configBuilder.setCompressionEnabled(true); 40 | //关闭安全验证 41 | configBuilder.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled); 42 | 43 | XMPPTCPConnection connection = new XMPPTCPConnection(configBuilder.build()); 44 | 45 | connection.addConnectionListener(new ConnectionListener() { 46 | @Override 47 | public void connected(XMPPConnection xmppConnection) { 48 | Log.e("connect","connected"); 49 | } 50 | 51 | @Override 52 | public void authenticated(XMPPConnection xmppConnection, boolean b) { 53 | Log.e("connect","authenticated"); 54 | } 55 | 56 | @Override 57 | public void connectionClosed() { 58 | Log.e("connect","connectionClosed"); 59 | } 60 | 61 | @Override 62 | public void connectionClosedOnError(Exception e) { 63 | Log.e("connect","connectionClosedOnError"); 64 | } 65 | 66 | @Override 67 | public void reconnectionSuccessful() { 68 | Log.e("connect","reconnectionSuccessful"); 69 | } 70 | 71 | @Override 72 | public void reconnectingIn(int i) { 73 | Log.e("connect","reconnectionIn: "+i); 74 | } 75 | 76 | @Override 77 | public void reconnectionFailed(Exception e) { 78 | Log.e("connect","reconnectionFailed"); 79 | } 80 | }); 81 | 82 | try { 83 | connection.connect(); 84 | } catch (SmackException | IOException | XMPPException e) { 85 | e.printStackTrace(); 86 | return null; 87 | } 88 | return connection; 89 | } 90 | 91 | 92 | public static boolean login(Context ctx, String username, String password) { 93 | XMPPTCPConnection connection = getXMPPConnection(ctx); 94 | if (connection == null) 95 | { 96 | Log.e("login","connection == null"); 97 | return false; 98 | } 99 | try { 100 | connection.login(username, password); 101 | DataWarehouse.setXMPPTCPConnection(ctx,connection); 102 | return true; 103 | } catch (XMPPException|SmackException|IOException e) { 104 | e.printStackTrace(); 105 | Log.e("login","login failure"); 106 | return false; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/data/ChatData.java: -------------------------------------------------------------------------------- 1 | package com.example.data; 2 | 3 | 4 | 5 | public class ChatData { 6 | public String user; // 用于账号 7 | public String text; // 聊天文本 8 | public boolean isOwner; // 是否是当前登录用户 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/data/ChatRoomData.java: -------------------------------------------------------------------------------- 1 | package com.example.data; 2 | 3 | import org.jivesoftware.smackx.disco.packet.DiscoverItems; 4 | 5 | 6 | public class ChatRoomData { 7 | public DiscoverItems.Item item; 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/data/ChatServiceData.java: -------------------------------------------------------------------------------- 1 | package com.example.data; 2 | 3 | import org.jivesoftware.smackx.muc.HostedRoom; 4 | 5 | 6 | public class ChatServiceData { 7 | public HostedRoom hostedRoom; 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/data/DataWarehouse.java: -------------------------------------------------------------------------------- 1 | package com.example.data; 2 | 3 | import android.content.Context; 4 | 5 | import com.example.MyApplication; 6 | 7 | import org.jivesoftware.smack.tcp.XMPPTCPConnection; 8 | 9 | 10 | public class DataWarehouse { 11 | public static MyApplication getGlobalData(Context ctx) 12 | { 13 | return (MyApplication) ctx.getApplicationContext(); 14 | } 15 | 16 | public static XMPPTCPConnection getXMPPTCPConnection(Context ctx) 17 | { 18 | return getGlobalData(ctx).xmpptcpConnection; 19 | } 20 | 21 | public static void setXMPPTCPConnection(Context ctx,XMPPTCPConnection conn) 22 | { 23 | getGlobalData(ctx).xmpptcpConnection = conn; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/data/LoginData.java: -------------------------------------------------------------------------------- 1 | package com.example.data; 2 | 3 | 4 | public class LoginData { 5 | public static String userName; 6 | public static String passWord; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/data/UserData.java: -------------------------------------------------------------------------------- 1 | package com.example.data; 2 | 3 | 4 | public class UserData { 5 | public String username; //用户名 6 | 7 | public UserData(String username) { 8 | this.username = username; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_add_friend.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | 25 | 26 | 27 |