# HG changeset patch # User Da Risk # Date 1426438983 -3600 # Node ID 197a85a35cba60fa591ddfd85d44082e82297100 # Parent 7d6f2526244a2c63af7fcb753a3e69977113b25e Move the app into an app submodule which respect the default gradle layout diff -r 7d6f2526244a -r 197a85a35cba AndroidManifest.xml --- a/AndroidManifest.xml Sun Mar 15 17:28:04 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -r 7d6f2526244a -r 197a85a35cba app/build.gradle --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/build.gradle Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,30 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 22 + buildToolsVersion "22.0.0" + + defaultConfig { + applicationId "com.beem.project.beem" + minSdkVersion 6 + targetSdkVersion 22 + versionCode 15 + versionName "0.1.8" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + lintOptions { + abortOnError false + } + +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} diff -r 7d6f2526244a -r 197a85a35cba app/libs/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/libs/README.txt Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,10 @@ +This directory contains the libraries used by BEEM. + +The principal one is asmack, a portage of the Smack library for the Android +platform. + +The source of the asmack library can be downloaded at +http://dev.beem-project.com/attachments/download/51/asmack-android-2.1-source-beem.zip + +See the file doc/asmack-beem/README.txt for more informations. + diff -r 7d6f2526244a -r 197a85a35cba app/libs/android-support-v13.jar Binary file app/libs/android-support-v13.jar has changed diff -r 7d6f2526244a -r 197a85a35cba app/libs/asmack-android-16-beem.jar Binary file app/libs/asmack-android-16-beem.jar has changed diff -r 7d6f2526244a -r 197a85a35cba app/libs/lcrypto-jdk16-146-20110415.jar Binary file app/libs/lcrypto-jdk16-146-20110415.jar has changed diff -r 7d6f2526244a -r 197a85a35cba app/proguard-rules.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/proguard-rules.pro Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /opt/android-sdk-update-manager/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/AndroidManifest.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/AndroidManifest.xml Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/Contact.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/Contact.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,46 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +parcelable Contact; diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/Message.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/Message.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,46 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +parcelable Message; diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/PresenceAdapter.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/PresenceAdapter.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,46 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +parcelable PresenceAdapter; diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/PrivacyListItem.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/PrivacyListItem.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,46 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +parcelable PrivacyListItem; diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/UserInfo.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/UserInfo.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,46 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +parcelable UserInfo; diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/aidl/IBeemConnectionListener.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/aidl/IBeemConnectionListener.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,88 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service.aidl; + +/** + * Interface to listen for connection events + * @author Da Risk + */ +interface IBeemConnectionListener { + + /** + * Callback to call when the connection is closed + */ + void connectionClosed(); + + /** + * Callback to call when the connection occurs + * @Deprecated + */ + //void onConnect(); + + //void connectionClosedOnError(in Exception e); + /** + * Callback to call when the connection is closed on error + */ + void connectionClosedOnError(); + + /** + * Callback to call when trying to reconnecting + */ + void reconnectingIn(in int seconds); + + /** + * Callback to call when the reconnection has failed + */ + void reconnectionFailed(); + + /** + * Callback to call when the reconnection is successfull + */ + void reconnectionSuccessful(); + + /** + * Callback to call when the connection Failed + */ + void connectionFailed(in String errorMsg); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/aidl/IBeemRosterListener.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/aidl/IBeemRosterListener.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,53 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service.aidl; + +import com.beem.project.beem.service.PresenceAdapter; + +interface IBeemRosterListener { + void onEntriesAdded(in List addresses); + void onEntriesUpdated(in List addresses); + void onEntriesDeleted(in List addresses); + void onPresenceChanged(in PresenceAdapter presence); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/aidl/IChat.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/aidl/IChat.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,121 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service.aidl; + +import com.beem.project.beem.service.Contact; +import com.beem.project.beem.service.Message; +import com.beem.project.beem.service.aidl.IMessageListener; + +/** + * An aidl interface for Chat session. + */ +interface IChat { + + /** + * Send a message. + * @param message the message to send + */ + void sendMessage(in com.beem.project.beem.service.Message message); + + /** + * Get the participant of the chat + * @return the participant + */ + Contact getParticipant(); + + /** + * Add a message listener. + * @param listener the listener to add. + */ + void addMessageListener(in IMessageListener listener); + + /** + * Remove a message listener. + * @param listener the listener to remove. + */ + void removeMessageListener(in IMessageListener listener); + + String getState(); + + void setOpen(in boolean isOpen); + + boolean isOpen(); + + int getUnreadMessageCount(); + + void setState(in String state); + + List getMessages(); + + /** + * Try to start an OTR session. + */ + void startOtrSession(); + + /** + * Stop the OTR session. + */ + void endOtrSession(); + + /** + * get local OTR key fingerprints. + */ + String getLocalOtrFingerprint(); + + + /** + * get remote OTR key fingerprints. + */ + String getRemoteOtrFingerprint(); + + void verifyRemoteFingerprint(in boolean ok); + + + /** + * get current OTR status. + */ + String getOtrStatus(); + + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/aidl/IChatManager.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/aidl/IChatManager.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,99 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service.aidl; + +import com.beem.project.beem.service.Contact; +import com.beem.project.beem.service.aidl.IChat; +import com.beem.project.beem.service.aidl.IMessageListener; +import com.beem.project.beem.service.aidl.IChatManagerListener; + +/** + * Aidl interface for a chat manager. + * The chat manager will manage all the chat sessions. + */ +interface IChatManager { + + /** + * Create a chat session with a contact. + * @param contact the contact to chat with + * @param listener the callback to call when a new message comes from this chat session + * @return the chat session + */ + IChat createChat(in Contact contact, in IMessageListener listener); + + /** + * Get an existing Chat session with a contact. + * @return null if the chat session does not exist. + */ + IChat getChat(in Contact contact); + + /** + * Destroy a chat session with a contact. + * @param chat the chat session + */ + void destroyChat(in IChat chat); + + /** + * @param chat the chat. + */ + void deleteChatNotification(in IChat chat); + + /** + * Register a callback to call when a new chat session is created. + * @param listener the callback to add + */ + void addChatCreationListener(in IChatManagerListener listener); + + /** + * Remove a callback for the creation of new chat session. + * @param listener the callback to remove. + */ + void removeChatCreationListener(in IChatManagerListener listener); + + /** + * Get a list of contact which we are currently chatting. + * @return list of contact. + */ + List getOpenedChatList(); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/aidl/IChatManagerListener.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/aidl/IChatManagerListener.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,61 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service.aidl; + +import com.beem.project.beem.service.aidl.IChat; + +/** + * Aidl interface for ChatManager listener. + * This listener will execute on events like creation of chat session. + */ +interface IChatManagerListener { + + /** + * Call when a new chat session is created. + * @param chat the created chat session + * @param locally true if the session is create by a chat manager. + */ + void chatCreated(IChat chat, boolean locally); + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/aidl/IContact.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/aidl/IContact.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,53 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service.aidl; + +interface IContact { + + String getJID(); + + void setJID(String mjid); + +} + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/aidl/IMessageListener.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/aidl/IMessageListener.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,69 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service.aidl; + +import com.beem.project.beem.service.Message; +import com.beem.project.beem.service.aidl.IChat; + +interface IMessageListener { + + /** + * This method is executed when a chat receive a message. + * @param chat the chat receiving the message. + * @param msg the message received in the chat. + */ + void processMessage(in IChat chat, in com.beem.project.beem.service.Message msg); + + /** + * This method is executed when a new ChatState is received by the chat. + * You can use IChat.getState() in order to get the new state. + * @param chat the chat changed. + */ + void stateChanged(in IChat chat); + /** + * This method is executed when the otr session status change. + * @param otrState the new state of otr session. + */ + void otrStateChanged(in String otrState); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/aidl/IPrivacyListListener.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/aidl/IPrivacyListListener.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,51 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service.aidl; + +import com.beem.project.beem.service.PrivacyListItem; + +interface IPrivacyListListener { + void updatedPrivacyList(in String listName); + void setPrivacyList(in String listName, in List listItem); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/aidl/IPrivacyListManager.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/aidl/IPrivacyListManager.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,66 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service.aidl; + +import com.beem.project.beem.service.PrivacyListItem; +import com.beem.project.beem.service.aidl.IPrivacyListListener; + +interface IPrivacyListManager { + void createPrivacyList(in String listName, in List items); + void removePrivacyList(in String listName); + void editPrivacyList(in String listName, in List items); + String getActivePrivacyList(); + String getDefaultPrivacyList(); + void setActivePrivacyList(in String listName); + void setDefaultPrivacyList(in String listName); + void declineActivePrivacyList(); + void declineDefaultPrivacyList(); + List getPrivacyLists(); + void blockUser(in String listName, in String jid); + List getBlockedUsersByList(in String listName); + List getBlockedGroupsByList(in String listName); + void addPrivacyListListener(in IPrivacyListListener listener); + void removePrivacyListListener(in IPrivacyListListener listener); +} + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/aidl/IRoster.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/aidl/IRoster.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,74 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service.aidl; + +import com.beem.project.beem.service.aidl.IBeemRosterListener; +import com.beem.project.beem.service.Contact; +import com.beem.project.beem.service.PresenceAdapter; + +interface IRoster { + + boolean addContact(in String user, in String name, in String[] groups); + + void deleteContact(in Contact contact); + + Contact getContact(in String jid); + void setContactName(in String jid, in String name); + + void createGroup(in String groupname); + + void addContactToGroup(in String groupName, in String jid); + + void removeContactFromGroup(in String groupName, in String jid); + + List getContactList(); + + List getGroupsNames(); + + PresenceAdapter getPresence(in String jid); + + void addRosterListener(in IBeemRosterListener listen); + void removeRosterListener(in IBeemRosterListener listen); + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/aidl/IXmppConnection.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/aidl/IXmppConnection.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,79 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service.aidl; + +import com.beem.project.beem.service.aidl.IRoster; +import com.beem.project.beem.service.aidl.IBeemConnectionListener; +import com.beem.project.beem.service.aidl.IChatManager; +import com.beem.project.beem.service.aidl.IPrivacyListManager; + +interface IXmppConnection { + + boolean connect(); + + boolean login(); + + boolean connectSync(); + + void connectAsync(); + + boolean disconnect(); + + IRoster getRoster(); + + void addConnectionListener(in IBeemConnectionListener listen); + void removeConnectionListener(in IBeemConnectionListener listen); + + boolean isAuthentificated(); + + IChatManager getChatManager(); + + void changeStatusAndPriority(in int status, in String msg, in int priority); + + void changeStatus(in int status, in String msg); + + IPrivacyListManager getPrivacyListManager(); + + String getErrorMessage(); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/aidl/com/beem/project/beem/service/aidl/IXmppFacade.aidl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/aidl/com/beem/project/beem/service/aidl/IXmppFacade.aidl Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,113 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service.aidl; + +import com.beem.project.beem.service.aidl.IXmppConnection; +import com.beem.project.beem.service.aidl.IRoster; +import com.beem.project.beem.service.aidl.IChatManager; +import com.beem.project.beem.service.aidl.IPrivacyListManager; +import com.beem.project.beem.service.PresenceAdapter; +import com.beem.project.beem.service.UserInfo; + +import android.net.Uri; + +interface IXmppFacade { + + /** + * Get the XmppConnection of the facade. + */ + IXmppConnection createConnection(); + + /** + * Get the roster of the user + */ + IRoster getRoster(); + + /** + * Connect and login synchronously on the server. + */ + void connectSync(); + + /** + * Connect and login asynchronously on the server. + */ + void connectAsync(); + + /** + * Disconnect from the server + */ + void disconnect(); + + /** + * Get the chat manager. + */ + IChatManager getChatManager(); + + /** + * Change the status of the user. + * @param status the status to set + * @param msg the message state to set + */ + void changeStatus(in int status, in String msg); + + void sendPresencePacket(in PresenceAdapter presence); + + /** + * make a jingle audio call + * @param jid the receiver id + */ + void call(in String jid); + + boolean publishAvatar(in Uri avatarUri); + + void disableAvatarPublishing(); + + /** + * Get the user informations. + * @return null if not connected + */ + UserInfo getUserInfo(); + + IPrivacyListManager getPrivacyListManager(); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/android/mms/util/SmileyParser.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/android/mms/util/SmileyParser.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mms.util; + +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import android.content.Context; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.ImageSpan; + +import com.beem.project.beem.R; + +/** + * A class for annotating a CharSequence with spans to convert textual emoticons + * to graphical ones. + */ +public class SmileyParser { + // Singleton stuff + private static SmileyParser sInstance; + public static SmileyParser getInstance() { return sInstance; } + public static void init(Context context) { + sInstance = new SmileyParser(context); + } + + private final Context mContext; + private final String[] mSmileyTexts; + private final Pattern mPattern; + private final HashMap mSmileyToRes; + + private SmileyParser(Context context) { + mContext = context; + mSmileyTexts = mContext.getResources().getStringArray(DEFAULT_SMILEY_TEXTS); + mSmileyToRes = buildSmileyToRes(); + mPattern = buildPattern(); + } + + static class Smileys { + private static final int[] sIconIds = { + R.drawable.emo_im_happy, + R.drawable.emo_im_sad, + R.drawable.emo_im_winking, + R.drawable.emo_im_tongue_sticking_out, + R.drawable.emo_im_surprised, + R.drawable.emo_im_kissing, + R.drawable.emo_im_yelling, + R.drawable.emo_im_cool, + R.drawable.emo_im_money_mouth, + R.drawable.emo_im_foot_in_mouth, + R.drawable.emo_im_embarrassed, + R.drawable.emo_im_angel, + R.drawable.emo_im_undecided, + R.drawable.emo_im_crying, + R.drawable.emo_im_lips_are_sealed, + R.drawable.emo_im_laughing, + R.drawable.emo_im_wtf, + R.drawable.emo_im_heart, + R.drawable.emo_im_mad, + R.drawable.emo_im_smirk, + R.drawable.emo_im_pokerface + }; + + public static int HAPPY = 0; + public static int SAD = 1; + public static int WINKING = 2; + public static int TONGUE_STICKING_OUT = 3; + public static int SURPRISED = 4; + public static int KISSING = 5; + public static int YELLING = 6; + public static int COOL = 7; + public static int MONEY_MOUTH = 8; + public static int FOOT_IN_MOUTH = 9; + public static int EMBARRASSED = 10; + public static int ANGEL = 11; + public static int UNDECIDED = 12; + public static int CRYING = 13; + public static int LIPS_ARE_SEALED = 14; + public static int LAUGHING = 15; + public static int WTF = 16; + public static int MAD = 17; + public static int HEART = 18; + public static int SMIRK = 19; + public static int POKERFACE = 20; + + public static int getSmileyResource(int which) { + return sIconIds[which]; + } + } + + // NOTE: if you change anything about this array, you must make the corresponding change + // to the string arrays: default_smiley_texts and default_smiley_names in res/values/arrays.xml + public static final int[] DEFAULT_SMILEY_RES_IDS = { + Smileys.getSmileyResource(Smileys.HAPPY), // 0 + Smileys.getSmileyResource(Smileys.SAD), // 1 + Smileys.getSmileyResource(Smileys.WINKING), // 2 + Smileys.getSmileyResource(Smileys.TONGUE_STICKING_OUT), // 3 + Smileys.getSmileyResource(Smileys.SURPRISED), // 4 + Smileys.getSmileyResource(Smileys.KISSING), // 5 + Smileys.getSmileyResource(Smileys.YELLING), // 6 + Smileys.getSmileyResource(Smileys.COOL), // 7 + Smileys.getSmileyResource(Smileys.MONEY_MOUTH), // 8 + Smileys.getSmileyResource(Smileys.FOOT_IN_MOUTH), // 9 + Smileys.getSmileyResource(Smileys.EMBARRASSED), // 10 + Smileys.getSmileyResource(Smileys.ANGEL), // 11 + Smileys.getSmileyResource(Smileys.UNDECIDED), // 12 + Smileys.getSmileyResource(Smileys.CRYING), // 13 + Smileys.getSmileyResource(Smileys.LIPS_ARE_SEALED), // 14 + Smileys.getSmileyResource(Smileys.LAUGHING), // 15 + Smileys.getSmileyResource(Smileys.WTF), // 16 + Smileys.getSmileyResource(Smileys.MAD), // 17 + Smileys.getSmileyResource(Smileys.HEART), // 18 + Smileys.getSmileyResource(Smileys.SMIRK), // 19 + Smileys.getSmileyResource(Smileys.POKERFACE), // 20 + }; + + public static final int DEFAULT_SMILEY_TEXTS = R.array.default_smiley_texts; + public static final int DEFAULT_SMILEY_NAMES = R.array.default_smiley_names; + + /** + * Builds the hashtable we use for mapping the string version + * of a smiley (e.g. ":-)") to a resource ID for the icon version. + */ + private HashMap buildSmileyToRes() { + if (DEFAULT_SMILEY_RES_IDS.length != mSmileyTexts.length) { + // Throw an exception if someone updated DEFAULT_SMILEY_RES_IDS + // and failed to update arrays.xml + throw new IllegalStateException("Smiley resource ID/text mismatch"); + } + + HashMap smileyToRes = + new HashMap(mSmileyTexts.length); + for (int i = 0; i < mSmileyTexts.length; i++) { + smileyToRes.put(mSmileyTexts[i], DEFAULT_SMILEY_RES_IDS[i]); + } + + return smileyToRes; + } + + /** + * Builds the regular expression we use to find smileys in {@link #addSmileySpans}. + */ + private Pattern buildPattern() { + // Set the StringBuilder capacity with the assumption that the average + // smiley is 3 characters long. + StringBuilder patternString = new StringBuilder(mSmileyTexts.length * 3); + + // Build a regex that looks like (:-)|:-(|...), but escaping the smilies + // properly so they will be interpreted literally by the regex matcher. + patternString.append('('); + for (String s : mSmileyTexts) { + patternString.append(Pattern.quote(s)); + patternString.append('|'); + } + // Replace the extra '|' with a ')' + patternString.replace(patternString.length() - 1, patternString.length(), ")"); + + return Pattern.compile(patternString.toString()); + } + + + /** + * Adds ImageSpans to a CharSequence that replace textual emoticons such + * as :-) with a graphical version. + * + * @param text A CharSequence possibly containing emoticons + * @return A CharSequence annotated with ImageSpans covering any + * recognized emoticons. + */ + public CharSequence addSmileySpans(CharSequence text) { + SpannableStringBuilder builder = new SpannableStringBuilder(text); + + Matcher matcher = mPattern.matcher(text); + while (matcher.find()) { + int resId = mSmileyToRes.get(matcher.group()); + builder.setSpan(new ImageSpan(mContext, resId), + matcher.start(), matcher.end(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + return builder; + } +} + + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/BeemApplication.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/BeemApplication.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,222 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ + +package com.beem.project.beem; + +import android.app.Application; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.text.TextUtils; + +import com.android.mms.util.SmileyParser; + +/** + * This class contains informations that needs to be global in the application. + * Theses informations must be necessary for the activities and the service. + * @author Da Risk + */ +public class BeemApplication extends Application { + + /* Constants for PREFERENCE_KEY + * The format of the Preference key is : + * $name_KEY = "$name" + */ + /** Preference key for account username. */ + public static final String ACCOUNT_USERNAME_KEY = "account_username"; + /** Preference key for account password. */ + public static final String ACCOUNT_PASSWORD_KEY = "account_password"; + /** Preference key set to true if using an Android account . */ + public static final String USE_SYSTEM_ACCOUNT_KEY = "use_system_account"; + + /** Preference key for Android account type . */ + public static final String ACCOUNT_SYSTEM_TYPE_KEY = "account_system_type"; + + /** Preference key set to true if using specific server details. */ + public static final String ACCOUNT_SPECIFIC_SERVER_KEY = "account_specific_server"; + + /** Preference key for specific server hostname. */ + public static final String ACCOUNT_SPECIFIC_SERVER_HOST_KEY = "account_specific_server_host"; + + /** Preference key for specific server port. */ + public static final String ACCOUNT_SPECIFIC_SERVER_PORT_KEY = "account_specific_server_port"; + + /** Preference key for status (available, busy, away, ...). */ + public static final String STATUS_KEY = "status"; + /** Preference key for status message. */ + public static final String STATUS_TEXT_KEY = "status_text"; + /** Preference key for connection resource . */ + public static final String CONNECTION_RESOURCE_KEY = "connection_resource"; + /** Preference key for connection priority. */ + public static final String CONNECTION_PRIORITY_KEY = "connection_priority"; + /** Preference key for the use of a proxy. */ + public static final String PROXY_USE_KEY = "proxy_use"; + /** Preference key for the type of proxy. */ + public static final String PROXY_TYPE_KEY = "proxy_type"; + /** Preference key for the proxy server. */ + public static final String PROXY_SERVER_KEY = "proxy_server"; + /** Preference key for the proxy port. */ + public static final String PROXY_PORT_KEY = "proxy_port"; + /** Preference key for the proxy username. */ + public static final String PROXY_USERNAME_KEY = "proxy_username"; + /** Preference key for the proxy password. */ + public static final String PROXY_PASSWORD_KEY = "proxy_password"; + /** Preference key for vibrate on notification. */ + public static final String NOTIFICATION_VIBRATE_KEY = "notification_vibrate"; + /** Preference key for notification sound. */ + public static final String NOTIFICATION_SOUND_KEY = "notification_sound"; + /** Preference key for smack debugging. */ + public static final String SMACK_DEBUG_KEY = "smack_debug"; + /** Preference key for full Jid for login. */ + public static final String FULL_JID_LOGIN_KEY = "full_jid_login"; + /** Preference key for display offline contact. */ + public static final String SHOW_OFFLINE_CONTACTS_KEY = "show_offline_contacts"; + /** Preference key for hide the groups. */ + public static final String HIDE_GROUPS_KEY = "hide_groups"; + /** Preference key for auto away enable. */ + public static final String USE_AUTO_AWAY_KEY = "use_auto_away"; + /** Preference key for auto away message. */ + public static final String AUTO_AWAY_MSG_KEY = "auto_away_msg"; + /** Preference key for compact chat ui. */ + public static final String USE_COMPACT_CHAT_UI_KEY = "use_compact_chat_ui"; + /** Preference key for history path on the SDCard. */ + public static final String CHAT_HISTORY_KEY = "settings_chat_history_path"; + /** Preference key to show the jid in the contact list. */ + public static final String SHOW_JID = "show_jid"; + + //TODO add the other one + + private boolean mIsConnected; + private boolean mIsAccountConfigured; + private boolean mPepEnabled; + private SharedPreferences mSettings; + private final PreferenceListener mPreferenceListener = new PreferenceListener(); + + /** + * Constructor. + */ + public BeemApplication() { + } + + @Override + public void onCreate() { + super.onCreate(); + mSettings = PreferenceManager.getDefaultSharedPreferences(this); + String login = mSettings.getString(BeemApplication.ACCOUNT_USERNAME_KEY, ""); + String password = mSettings.getString(BeemApplication.ACCOUNT_PASSWORD_KEY, ""); + boolean useSystemAccount = mSettings.getBoolean(BeemApplication.USE_SYSTEM_ACCOUNT_KEY, false); + mIsAccountConfigured = !TextUtils.isEmpty(login) && (useSystemAccount || !TextUtils.isEmpty((password))); + mSettings.registerOnSharedPreferenceChangeListener(mPreferenceListener); + SmileyParser.init(this); + } + + @Override + public void onTerminate() { + super.onTerminate(); + mSettings.unregisterOnSharedPreferenceChangeListener(mPreferenceListener); + } + + /** + * Tell if Beem is connected to a XMPP server. + * @return false if not connected. + */ + public boolean isConnected() { + return mIsConnected; + } + + /** + * Set the status of the connection to a XMPP server of BEEM. + * @param isConnected set for the state of the connection. + */ + public void setConnected(boolean isConnected) { + mIsConnected = isConnected; + } + + /** + * Tell if a XMPP account is configured. + * @return false if there is no account configured. + */ + public boolean isAccountConfigured() { + return mIsAccountConfigured; + } + + /** + * Enable Pep in the application context. + * + * @param enabled true to enable pep + */ + public void setPepEnabled(boolean enabled) { + mPepEnabled = enabled; + } + + /** + * Check if Pep is enabled. + * + * @return true if enabled + */ + public boolean isPepEnabled() { + return mPepEnabled; + } + + /** + * A listener for all the change in the preference file. It is used to maintain the global state of the application. + */ + private class PreferenceListener implements SharedPreferences.OnSharedPreferenceChangeListener { + + /** + * Constructor. + */ + public PreferenceListener() { + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (BeemApplication.ACCOUNT_USERNAME_KEY.equals(key) || BeemApplication.ACCOUNT_PASSWORD_KEY.equals(key) || BeemApplication.USE_SYSTEM_ACCOUNT_KEY.equals(key)) { + String login = mSettings.getString(BeemApplication.ACCOUNT_USERNAME_KEY, ""); + String password = mSettings.getString(BeemApplication.ACCOUNT_PASSWORD_KEY, ""); + boolean useSystemAccount = mSettings.getBoolean(BeemApplication.USE_SYSTEM_ACCOUNT_KEY, false); + mIsAccountConfigured = !TextUtils.isEmpty(login) && (useSystemAccount || !TextUtils.isEmpty((password))); + } + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/BeemService.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/BeemService.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,657 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem; + +import java.io.File; +import java.io.IOException; +import java.security.GeneralSecurityException; + +import javax.net.ssl.SSLContext; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.net.ConnectivityManager; +import android.net.Uri; +import android.os.IBinder; +import android.os.RemoteException; +import android.preference.PreferenceManager; +import android.provider.Settings; +import android.util.Log; + +import com.beem.project.beem.service.XmppConnectionAdapter; +import com.beem.project.beem.service.XmppFacade; +import com.beem.project.beem.service.aidl.IXmppFacade; +import com.beem.project.beem.service.auth.AccountAuthenticator; +import com.beem.project.beem.service.auth.PreferenceAuthenticator; +import com.beem.project.beem.smack.avatar.AvatarMetadataProvider; +import com.beem.project.beem.smack.avatar.AvatarProvider; +import com.beem.project.beem.smack.ping.PingExtension; +import com.beem.project.beem.smack.sasl.SASLGoogleOAuth2Mechanism; +import com.beem.project.beem.smack.sasl.ScramSaslMechanism; +import com.beem.project.beem.utils.BeemBroadcastReceiver; +import com.beem.project.beem.utils.BeemConnectivity; +import com.beem.project.beem.utils.Status; + +import de.duenndns.ssl.MemorizingTrustManager; + +import org.jivesoftware.smack.ConnectionConfiguration; +import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.Roster.SubscriptionMode; +import org.jivesoftware.smack.SASLAuthentication; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smack.proxy.ProxyInfo; +import org.jivesoftware.smack.proxy.ProxyInfo.ProxyType; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.entitycaps.EntityCapsManager; +import org.jivesoftware.smackx.entitycaps.SimpleDirectoryPersistentCache; +import org.jivesoftware.smackx.entitycaps.packet.CapsExtension; +import org.jivesoftware.smackx.packet.ChatStateExtension; +import org.jivesoftware.smackx.provider.CapsExtensionProvider; +import org.jivesoftware.smackx.provider.DelayInfoProvider; +import org.jivesoftware.smackx.provider.DiscoverInfoProvider; +import org.jivesoftware.smackx.provider.DiscoverItemsProvider; +import org.jivesoftware.smackx.pubsub.provider.EventProvider; +import org.jivesoftware.smackx.pubsub.provider.ItemProvider; +import org.jivesoftware.smackx.pubsub.provider.ItemsProvider; +import org.jivesoftware.smackx.pubsub.provider.PubSubProvider; + +/** + * This class is for the Beem service. + * It must contains every global informations needed to maintain the background service. + * The connection to the xmpp server will be made asynchronously when the service + * will start. + * @author darisk + */ +public class BeemService extends Service { + + /** The id to use for status notification. */ + public static final int NOTIFICATION_STATUS_ID = 100; + + private static final String TAG = "BeemService"; + private static final int DEFAULT_XMPP_PORT = 5222; + + private NotificationManager mNotificationManager; + private XmppConnectionAdapter mConnection; + private SharedPreferences mSettings; + private String mLogin; + private String mHost; + private String mService; + private int mPort; + private ConnectionConfiguration mConnectionConfiguration; + private IXmppFacade.Stub mBind; + + private BeemBroadcastReceiver mReceiver = new BeemBroadcastReceiver(); + private BeemServiceBroadcastReceiver mOnOffReceiver = new BeemServiceBroadcastReceiver(); + private BeemServicePreferenceListener mPreferenceListener = new BeemServicePreferenceListener(); + + private boolean mOnOffReceiverIsRegistered; + + private SSLContext sslContext; + + /** + * Constructor. + */ + public BeemService() { + } + + /** + * Initialize the connection. + */ + private void initConnectionConfig() { + // TODO add an option for this ? +// SmackConfiguration.setPacketReplyTimeout(30000); + ProxyInfo proxyInfo = getProxyConfiguration(); + boolean useSystemAccount = mSettings.getBoolean(BeemApplication.USE_SYSTEM_ACCOUNT_KEY, false); + if (useSystemAccount) { + // when using system account, using SPECIFIC_SERVER settings is not a supported configuration. + SASLAuthentication.supportSASLMechanism(SASLGoogleOAuth2Mechanism.MECHANISM_NAME); + String accountType = mSettings.getString(BeemApplication.ACCOUNT_SYSTEM_TYPE_KEY, ""); + String accountName = mSettings.getString(BeemApplication.ACCOUNT_USERNAME_KEY, ""); + Account account = getAccount(accountName, accountType); + if (account == null) { + mSettings.edit().putString(BeemApplication.ACCOUNT_USERNAME_KEY, "").commit(); + } else + mConnectionConfiguration = getConnectionConfigurationForAccount(account, proxyInfo); + } + if (!useSystemAccount || mConnectionConfiguration == null) { + SASLAuthentication.unsupportSASLMechanism(SASLGoogleOAuth2Mechanism.MECHANISM_NAME); + if (mSettings.getBoolean(BeemApplication.ACCOUNT_SPECIFIC_SERVER_KEY, false)) + mConnectionConfiguration = new ConnectionConfiguration(mHost, mPort, mService, proxyInfo); + else + mConnectionConfiguration = new ConnectionConfiguration(mService, proxyInfo); + mConnectionConfiguration.setCallbackHandler(new PreferenceAuthenticator(this)); + } + + if (mSettings.getBoolean("settings_key_xmpp_tls_use", false) + || mSettings.getBoolean("settings_key_gmail", false)) { + mConnectionConfiguration.setSecurityMode(SecurityMode.required); + } + if (mSettings.getBoolean(BeemApplication.SMACK_DEBUG_KEY, false)) + mConnectionConfiguration.setDebuggerEnabled(true); + mConnectionConfiguration.setSendPresence(false); + mConnectionConfiguration.setRosterLoadedAtLogin(false); + // maybe not the universal path, but it works on most devices (Samsung Galaxy, Google Nexus One) + mConnectionConfiguration.setTruststoreType("BKS"); + mConnectionConfiguration.setTruststorePath("/system/etc/security/cacerts.bks"); + if (sslContext != null) + mConnectionConfiguration.setCustomSSLContext(sslContext); + } + + /** + * Get the save proxy configuration. + * + * @return the proxy configuration + */ + private ProxyInfo getProxyConfiguration() { + boolean useProxy = mSettings.getBoolean(BeemApplication.PROXY_USE_KEY, false); + if (useProxy) { + String stype = mSettings.getString(BeemApplication.PROXY_TYPE_KEY, "HTTP"); + String phost = mSettings.getString(BeemApplication.PROXY_SERVER_KEY, ""); + String puser = mSettings.getString(BeemApplication.PROXY_USERNAME_KEY, ""); + String ppass = mSettings.getString(BeemApplication.PROXY_PASSWORD_KEY, ""); + int pport = Integer.parseInt(mSettings.getString(BeemApplication.PROXY_PORT_KEY, "1080")); + ProxyInfo.ProxyType type = ProxyType.valueOf(stype); + return new ProxyInfo(type, phost, pport, puser, ppass); + } else { + return ProxyInfo.forNoProxy(); + } + } + + /** + * Get the connection configuration for an Android system account. + * + * @param account the Android account + * @param proxy the proxy to use + * @return the ConnectionConfiguration or null if the account is not supported + */ + private ConnectionConfiguration getConnectionConfigurationForAccount(Account account, ProxyInfo proxy) { + ConnectionConfiguration result = null; + if ("com.google".equals(account.type)) { + result = new ConnectionConfiguration("talk.google.com", DEFAULT_XMPP_PORT, proxy); + result.setServiceName(StringUtils.parseServer(account.name)); + result.setCallbackHandler(new AccountAuthenticator(this, account)); + result.setSecurityMode(SecurityMode.required); + } + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public IBinder onBind(Intent intent) { + Log.d(TAG, "ONBIND()"); + return mBind; + } + + @Override + public boolean onUnbind(Intent intent) { + Log.d(TAG, "ONUNBIND()"); + if (mConnection != null && !mConnection.getAdaptee().isConnected()) { + this.stopSelf(); + } + return true; + } + + + /** + * {@inheritDoc} + */ + @Override + public void onCreate() { + super.onCreate(); + registerReceiver(mReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + mSettings = PreferenceManager.getDefaultSharedPreferences(this); + mSettings.registerOnSharedPreferenceChangeListener(mPreferenceListener); + if (mSettings.getBoolean(BeemApplication.USE_AUTO_AWAY_KEY, false)) { + mOnOffReceiverIsRegistered = true; + registerReceiver(mOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); + registerReceiver(mOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON)); + } + String tmpJid = mSettings.getString(BeemApplication.ACCOUNT_USERNAME_KEY, "").trim(); + mLogin = StringUtils.parseName(tmpJid); + boolean useSystemAccount = mSettings.getBoolean(BeemApplication.USE_SYSTEM_ACCOUNT_KEY, false); + mPort = DEFAULT_XMPP_PORT; + mService = StringUtils.parseServer(tmpJid); + mHost = mService; + initMemorizingTrustManager(); + + if (mSettings.getBoolean(BeemApplication.ACCOUNT_SPECIFIC_SERVER_KEY, false)) { + mHost = mSettings.getString(BeemApplication.ACCOUNT_SPECIFIC_SERVER_HOST_KEY, "").trim(); + if ("".equals(mHost)) + mHost = mService; + String tmpPort = mSettings.getString(BeemApplication.ACCOUNT_SPECIFIC_SERVER_PORT_KEY, "5222"); + if (!"".equals(tmpPort)) + mPort = Integer.parseInt(tmpPort); + } + if (mSettings.getBoolean(BeemApplication.FULL_JID_LOGIN_KEY, false) + || "gmail.com".equals(mService) || "googlemail.com".equals(mService) + || useSystemAccount) { + mLogin = tmpJid; + } + + configure(ProviderManager.getInstance()); + + mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + Roster.setDefaultSubscriptionMode(SubscriptionMode.manual); + mBind = new XmppFacade(this); + Log.d(TAG, "Create BeemService"); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDestroy() { + super.onDestroy(); + mNotificationManager.cancelAll(); + unregisterReceiver(mReceiver); + mSettings.unregisterOnSharedPreferenceChangeListener(mPreferenceListener); + if (mOnOffReceiverIsRegistered) + unregisterReceiver(mOnOffReceiver); + if (mConnection.isAuthentificated() && BeemConnectivity.isConnected(this)) + mConnection.disconnect(); + Log.i(TAG, "Stopping the service"); + } + + /** + * {@inheritDoc} + */ + @Override + public void onStart(Intent intent, int startId) { + super.onStart(intent, startId); + Log.d(TAG, "onStart"); + createConnectAsync(); + } + + /** + * Create the XmppConnectionAdapter. + * This method makes a network request so it must not be called on the main thread. + * @return the connection + */ + public XmppConnectionAdapter createConnection() { + if (mConnection == null) { + initConnectionConfig(); + mConnection = new XmppConnectionAdapter(mConnectionConfiguration, mLogin, null, this); + } + return mConnection; + } + + /** + * Show a notification using the preference of the user. + * @param id the id of the notification. + * @param notif the notification to show + */ + public void sendNotification(int id, Notification notif) { + if (mSettings.getBoolean(BeemApplication.NOTIFICATION_VIBRATE_KEY, true)) + notif.defaults |= Notification.DEFAULT_VIBRATE; + notif.ledARGB = 0xff0000ff; // Blue color + notif.ledOnMS = 1000; + notif.ledOffMS = 1000; + notif.flags |= Notification.FLAG_SHOW_LIGHTS; + String ringtoneStr = mSettings.getString(BeemApplication.NOTIFICATION_SOUND_KEY, + Settings.System.DEFAULT_NOTIFICATION_URI.toString()); + notif.sound = Uri.parse(ringtoneStr); + mNotificationManager.notify(id, notif); + } + + /** + * Delete a notification. + * @param id the id of the notification + */ + public void deleteNotification(int id) { + mNotificationManager.cancel(id); + } + + /** + * Reset the status to online after a disconnect. + */ + public void resetStatus() { + Editor edit = mSettings.edit(); + edit.putInt(BeemApplication.STATUS_KEY, 1); + edit.commit(); + } + + /** + * Initialize Jingle from an XmppConnectionAdapter. + * @param adaptee XmppConnection used for jingle. + */ + public void initJingle(XMPPConnection adaptee) { + } + + /** + * Return a bind to an XmppFacade instance. + * @return IXmppFacade a bind to an XmppFacade instance + */ + public IXmppFacade getBind() { + return mBind; + } + + /** + * Get the preference of the service. + * @return the preference + */ + public SharedPreferences getServicePreference() { + return mSettings; + } + + /** + * Get the notification manager system service. + * @return the notification manager service. + */ + public NotificationManager getNotificationManager() { + return mNotificationManager; + } + + /** + * Utility method to create and make a connection asynchronously. + */ + private synchronized void createConnectAsync() { + if (mConnection == null) { + new Thread(new Runnable() { + + @Override + public void run() { + createConnection(); + connectAsync(); + } + }).start(); + } else + connectAsync(); + } + + /** + * Utility method to connect asynchronously. + */ + private void connectAsync() { + try { + mConnection.connectAsync(); + } catch (RemoteException e) { + Log.w(TAG, "unable to connect", e); + } + } + + /** + * Get the specified Android account. + * + * @param accountName the account name + * @param accountType the account type + * + * @return the account or null if it does not exist + */ + private Account getAccount(String accountName, String accountType) { + AccountManager am = AccountManager.get(this); + for (Account a : am.getAccountsByType(accountType)) { + if (a.name.equals(accountName)) { + return a; + } + } + return null; + } + + /** + * Install the MemorizingTrustManager in the ConnectionConfiguration of Smack. + */ + private void initMemorizingTrustManager() { + try { + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, MemorizingTrustManager.getInstanceList(this), + new java.security.SecureRandom()); + } catch (GeneralSecurityException e) { + Log.w(TAG, "Unable to use MemorizingTrustManager", e); + } + } + + /** + * A sort of patch from this thread: http://www.igniterealtime.org/community/thread/31118. Avoid ClassCastException + * by bypassing the classloading shit of Smack. + * @param pm The ProviderManager. + */ + private void configure(ProviderManager pm) { + Log.d(TAG, "configure"); + // Service Discovery # Items + pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider()); + // Service Discovery # Info + pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); + + // Privacy + //pm.addIQProvider("query", "jabber:iq:privacy", new PrivacyProvider()); + // Delayed Delivery only the new version + pm.addExtensionProvider("delay", "urn:xmpp:delay", new DelayInfoProvider()); + + // Service Discovery # Items + pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider()); + // Service Discovery # Info + pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider()); + + // Chat State + ChatStateExtension.Provider chatState = new ChatStateExtension.Provider(); + pm.addExtensionProvider("active", "http://jabber.org/protocol/chatstates", chatState); + pm.addExtensionProvider("composing", "http://jabber.org/protocol/chatstates", + chatState); + pm.addExtensionProvider("paused", "http://jabber.org/protocol/chatstates", chatState); + pm.addExtensionProvider("inactive", "http://jabber.org/protocol/chatstates", chatState); + pm.addExtensionProvider("gone", "http://jabber.org/protocol/chatstates", chatState); + // capabilities + pm.addExtensionProvider(CapsExtension.NODE_NAME, CapsExtension.XMLNS, new CapsExtensionProvider()); + + //Pubsub + pm.addIQProvider("pubsub", "http://jabber.org/protocol/pubsub", new PubSubProvider()); + pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub", new ItemsProvider()); + pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub", new ItemsProvider()); + pm.addExtensionProvider("item", "http://jabber.org/protocol/pubsub", new ItemProvider()); + + pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub#event", new ItemsProvider()); + pm.addExtensionProvider("item", "http://jabber.org/protocol/pubsub#event", new ItemProvider()); + pm.addExtensionProvider("event", "http://jabber.org/protocol/pubsub#event", new EventProvider()); + //TODO rajouter les manquants pour du full pubsub + + + //PEP avatar + pm.addExtensionProvider("metadata", "urn:xmpp:avatar:metadata", new AvatarMetadataProvider()); + pm.addExtensionProvider("data", "urn:xmpp:avatar:data", new AvatarProvider()); + +// PEPProvider pep = new PEPProvider(); +// AvatarMetadataProvider avaMeta = new AvatarMetadataProvider(); +// pep.registerPEPParserExtension("urn:xmpp:avatar:metadata", avaMeta); +// pm.addExtensionProvider("event", "http://jabber.org/protocol/pubsub#event", pep); + + // ping + pm.addIQProvider(PingExtension.ELEMENT, PingExtension.NAMESPACE, PingExtension.class); + + /* + // Private Data Storage + pm.addIQProvider("query", "jabber:iq:private", new PrivateDataManager.PrivateDataIQProvider()); + // Time + try { + pm.addIQProvider("query", "jabber:iq:time", Class.forName("org.jivesoftware.smackx.packet.Time")); + } catch (ClassNotFoundException e) { + Log.w("TestClient", "Can't load class for org.jivesoftware.smackx.packet.Time"); + } + // Roster Exchange + pm.addExtensionProvider("x", "jabber:x:roster", new RosterExchangeProvider()); + // Message Events + pm.addExtensionProvider("x", "jabber:x:event", new MessageEventProvider()); + // XHTML + pm.addExtensionProvider("html", "http://jabber.org/protocol/xhtml-im", new XHTMLExtensionProvider()); + // Group Chat Invitations + pm.addExtensionProvider("x", "jabber:x:conference", new GroupChatInvitation.Provider()); + // Data Forms + pm.addExtensionProvider("x", "jabber:x:data", new DataFormProvider()); + // MUC User + pm.addExtensionProvider("x", "http://jabber.org/protocol/muc#user", new MUCUserProvider()); + // MUC Admin + pm.addIQProvider("query", "http://jabber.org/protocol/muc#admin", new MUCAdminProvider()); + // MUC Owner + pm.addIQProvider("query", "http://jabber.org/protocol/muc#owner", new MUCOwnerProvider()); + // Version + try { + pm.addIQProvider("query", "jabber:iq:version", Class.forName("org.jivesoftware.smackx.packet.Version")); + } catch (ClassNotFoundException e) { + // Not sure what's happening here. + Log.w("TestClient", "Can't load class for org.jivesoftware.smackx.packet.Version"); + } + // VCard + pm.addIQProvider("vCard", "vcard-temp", new VCardProvider()); + // Offline Message Requests + pm.addIQProvider("offline", "http://jabber.org/protocol/offline", new OfflineMessageRequest.Provider()); + // Offline Message Indicator + pm.addExtensionProvider("offline", "http://jabber.org/protocol/offline", new OfflineMessageInfo.Provider()); + // Last Activity + pm.addIQProvider("query", "jabber:iq:last", new LastActivity.Provider()); + // User Search + pm.addIQProvider("query", "jabber:iq:search", new UserSearch.Provider()); + // SharedGroupsInfo + pm.addIQProvider("sharedgroup", "http://www.jivesoftware.org/protocol/sharedgroup", + new SharedGroupsInfo.Provider()); + // JEP-33: Extended Stanza Addressing + pm.addExtensionProvider("addresses", "http://jabber.org/protocol/address", new MultipleAddressesProvider()); + // FileTransfer + pm.addIQProvider("si", "http://jabber.org/protocol/si", new StreamInitiationProvider()); + pm.addIQProvider("query", "http://jabber.org/protocol/bytestreams", new BytestreamsProvider()); + pm.addIQProvider("open", "http://jabber.org/protocol/ibb", new IBBProviders.Open()); + pm.addIQProvider("close", "http://jabber.org/protocol/ibb", new IBBProviders.Close()); + pm.addExtensionProvider("data", "http://jabber.org/protocol/ibb", new IBBProviders.Data()); + + pm.addIQProvider("command", COMMAND_NAMESPACE, new AdHocCommandDataProvider()); + pm.addExtensionProvider("malformed-action", COMMAND_NAMESPACE, + new AdHocCommandDataProvider.MalformedActionError()); + pm.addExtensionProvider("bad-locale", COMMAND_NAMESPACE, + new AdHocCommandDataProvider.BadLocaleError()); + pm.addExtensionProvider("bad-payload", COMMAND_NAMESPACE, + new AdHocCommandDataProvider.BadPayloadError()); + pm.addExtensionProvider("bad-sessionid", COMMAND_NAMESPACE, + new AdHocCommandDataProvider.BadSessionIDError()); + pm.addExtensionProvider("session-expired", COMMAND_NAMESPACE, + new AdHocCommandDataProvider.SessionExpiredError()); + */ + + /* register additionnals sasl mechanisms */ + SASLAuthentication.registerSASLMechanism(SASLGoogleOAuth2Mechanism.MECHANISM_NAME, + SASLGoogleOAuth2Mechanism.class); + SASLAuthentication.registerSASLMechanism(ScramSaslMechanism.MECHANISM_NAME, + ScramSaslMechanism.class); + + SASLAuthentication.supportSASLMechanism(ScramSaslMechanism.MECHANISM_NAME); + // Configure entity caps manager. This must be done only once + File f = new File(getCacheDir(), "entityCaps"); + f.mkdirs(); + try { + EntityCapsManager.setPersistentCache(new SimpleDirectoryPersistentCache(f)); + } catch (IllegalStateException e) { + Log.v(TAG, "EntityCapsManager already initialized", e); + } catch (IOException e) { + Log.w(TAG, "EntityCapsManager not able to reuse persistent cache"); + } + } + + /** + * Listen on preference changes. + */ + private class BeemServicePreferenceListener implements SharedPreferences.OnSharedPreferenceChangeListener { + + /** + * ctor. + */ + public BeemServicePreferenceListener() { + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (BeemApplication.USE_AUTO_AWAY_KEY.equals(key)) { + if (sharedPreferences.getBoolean(BeemApplication.USE_AUTO_AWAY_KEY, false)) { + mOnOffReceiverIsRegistered = true; + registerReceiver(mOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); + registerReceiver(mOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON)); + } else { + mOnOffReceiverIsRegistered = false; + unregisterReceiver(mOnOffReceiver); + } + } + } + } + + /** + * Listen on some Intent broadcast, ScreenOn and ScreenOff. + */ + private class BeemServiceBroadcastReceiver extends BroadcastReceiver { + + private String mOldStatus; + private int mOldMode; + + /** + * Constructor. + */ + public BeemServiceBroadcastReceiver() { + } + + @Override + public void onReceive(final Context context, final Intent intent) { + String intentAction = intent.getAction(); + if (intentAction.equals(Intent.ACTION_SCREEN_OFF)) { + mOldMode = mConnection.getPreviousMode(); + mOldStatus = mConnection.getPreviousStatus(); + if (mConnection.isAuthentificated()) + mConnection.changeStatus(Status.CONTACT_STATUS_AWAY, + mSettings.getString(BeemApplication.AUTO_AWAY_MSG_KEY, "Away")); + } else if (intentAction.equals(Intent.ACTION_SCREEN_ON)) { + if (mConnection.isAuthentificated()) + mConnection.changeStatus(mOldMode, mOldStatus); + } + } + } +} + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/otr/BeemOtrManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/otr/BeemOtrManager.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,210 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2011 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ +package com.beem.project.beem.otr; + +import java.io.IOException; +import java.security.KeyPair; +import java.util.HashMap; +import java.util.Map; + +import net.java.otr4j.OtrEngine; +import net.java.otr4j.OtrEngineHost; +import net.java.otr4j.OtrEngineImpl; +import net.java.otr4j.OtrEngineListener; +import net.java.otr4j.OtrException; +import net.java.otr4j.OtrKeyManagerImpl; +import net.java.otr4j.OtrPolicy; +import net.java.otr4j.OtrPolicyImpl; +import net.java.otr4j.session.SessionID; +import net.java.otr4j.session.SessionStatus; +import android.util.Log; + +import com.beem.project.beem.service.ChatAdapter; + +/** + * BeemOtrManager. + */ +public class BeemOtrManager implements OtrEngineHost { + + private static final String TAG = "BeemOtrEngineHostImpl"; + private static BeemOtrManager INSTANCE; + //We will have a global policy for Beem as long as we won't need to modify the policy per chat. + private static final OtrPolicy mGlobalPolicy = new OtrPolicyImpl(OtrPolicy.ALLOW_V2 | OtrPolicy.ERROR_START_AKE); + + private OtrEngine mOtrEngine; + private OtrKeyManagerImpl mOtrKeyManager; + + //Map of chat, needed because of the message injection + private final Map mChats = new HashMap(); + + /** + * Private constructor prevents instantiation from other classes. + */ + private BeemOtrManager() { + mOtrEngine = new OtrEngineImpl(this); + mOtrEngine.addOtrEngineListener(new BeemOtrListener()); + try { + mOtrKeyManager = new OtrKeyManagerImpl("/sdcard/beem.keystore"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * getOtrManager. + * @return OtrEngine + */ + public OtrEngine getOtrManager() { + return mOtrEngine; + } + + /** + * BeemOtrManager.getInstance. + * @return BeemOtrManager + */ + public static BeemOtrManager getInstance() { + if (INSTANCE == null) + INSTANCE = new BeemOtrManager(); + return INSTANCE; + } + + /** + * We must call addChat before stating a new otr session because we will need the chat instance for message + * injection. + * @param sessionID the otr sessionID. + * @param chat instance. + */ + public void addChat(final SessionID sessionID, final ChatAdapter chat) { + mChats.put(sessionID, chat); + Log.d(TAG, "adding new OTR session " + sessionID); + } + + /** + * We must remove the chat from the map after we ended the corresponding otr session. + * @param sessionID the otr sessionID to remove. + */ + public void removeChat(final SessionID sessionID) { + mChats.remove(sessionID); + } + + /** + * get the fingerprint of the remote part. + * @param sessionID the otr session + * @return a string containing the fingerprint + */ + public String getRemoteFingerprint(final SessionID sessionID) { + return mOtrKeyManager.getRemoteFingerprint(sessionID); + } + + /** + * set the remote fingerprint as verified. + * @param sessionId the current otr session + */ + public void verifyRemoteFingerprint(final SessionID sessionId) { + mOtrKeyManager.verify(sessionId); + } + + /** + * unsetthe remote fingerprint as verified. + * @param sessionId the current otr session + */ + public void unverifyRemoteFingerprint(final SessionID sessionId) { + mOtrKeyManager.unverify(sessionId); + } + + /** + * get the local fingerprint. + * @param sessionID the otr session + * @return a string containing the fingerprint + */ + public String getLocalFingerprint(final SessionID sessionID) { + return mOtrKeyManager.getLocalFingerprint(sessionID); + } + + @Override + public void injectMessage(SessionID sessionID, String msg) { + ChatAdapter chat = mChats.get(sessionID); + chat.injectMessage(msg); + } + + @Override + public void showWarning(SessionID sessionID, String warning) { + Log.d(TAG, "Warning for " + sessionID + " : " + warning); + } + + @Override + public void showError(SessionID sessionID, String error) { + Log.d(TAG, "Error for " + sessionID + " : " + error); + } + + @Override + public OtrPolicy getSessionPolicy(SessionID sessionID) { + return mGlobalPolicy; + } + + @Override + public KeyPair getKeyPair(SessionID sessionID) { + KeyPair kp = mOtrKeyManager.loadLocalKeyPair(sessionID); + + if (kp != null) + return kp; + + mOtrKeyManager.generateLocalKeyPair(sessionID); + return mOtrKeyManager.loadLocalKeyPair(sessionID); + } + + /** + * BeemOtrListener. + */ + private class BeemOtrListener implements OtrEngineListener { + + @Override + public void sessionStatusChanged(final SessionID sessionID) { + Log.d(TAG, "OTR Status changed for " + sessionID + " : " + mOtrEngine.getSessionStatus(sessionID)); + if (mOtrKeyManager.loadRemotePublicKey(sessionID) == null) { + mOtrKeyManager.savePublicKey(sessionID, mOtrEngine.getRemotePublicKey(sessionID)); + } + + SessionStatus status = mOtrEngine.getSessionStatus(sessionID); + + if (status.equals(SessionStatus.ENCRYPTED) && mOtrKeyManager.isVerified(sessionID)) { + mChats.get(sessionID).otrStateChanged("AUTHENTICATED"); + } else { + if (status.equals(SessionStatus.FINISHED)) { + try { + mChats.get(sessionID).localEndOtrSession(); + } catch (OtrException e) { + Log.w(TAG, "error when closing local otr session", e); + } + } + else { + mChats.get(sessionID).otrStateChanged(status.toString()); + } + } + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,49 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ + +/** + * This package contains BEEM's entry points. + */ +package com.beem.project.beem; + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/providers/AvatarProvider.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/providers/AvatarProvider.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,270 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.providers; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import android.util.Log; + + +/** + * A simple content provider we expose the differents avatar downloaded. + * + */ +public class AvatarProvider extends ContentProvider { + + /** The content uri of this provider. */ + public static final Uri CONTENT_URI = + Uri.parse("content://com.beem.project.beem.providers.avatarprovider"); + + /** The MIME type of a CONTENT_URI directory of Beem avatars. */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.com.beem.project.beem.avatar"; + + /** The MIME type of a CONTENT_URI subdirectory of a single Beem avatar. */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.com.beem.project.beem.avatar"; + + /** + * Id of the user avatar. + */ + public static final String MY_AVATAR_ID = "my_avatar"; + + + private static final String TAG = AvatarProvider.class.getSimpleName(); + private static final String AUTHORITY = "com.beem.project.beem.providers.avatarprovider"; + + private static String[] columnNames = new String[] {Columns.ID, Columns.DATA}; + + private static final int AVATAR = 1; + private static final int AVATAR_ID = 2; + private static final UriMatcher URIMATCHER = new UriMatcher(AVATAR); + + static + { + URIMATCHER.addURI(AUTHORITY, "*", AVATAR_ID); + // should not be needed if we pass AVATAR on the constructor but it does not work + URIMATCHER.addURI(AUTHORITY, null, AVATAR); + } + + private String mDataPath; + + /** + * Create an AvatarProvider. + */ + public AvatarProvider() { + } + + @Override + public boolean onCreate() { + File cacheDir = Environment.getExternalStorageDirectory(); + File dataPath = new File(cacheDir, "/Android/data/com.beem.project.beem/cache/avatar"); + dataPath.mkdirs(); + mDataPath = dataPath.getAbsolutePath(); + return true; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) + throws FileNotFoundException { + return openFileHelper(uri, mode); + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + MatrixCursor c = new MatrixCursor(columnNames); + int match = URIMATCHER.match(uri); + switch (match) { + case AVATAR: + File[] files = new File(mDataPath).listFiles(); + if (files != null) { + for (File f : files) { + c.newRow().add(f.getName()).add(f.getAbsolutePath()); + } + } + break; + case AVATAR_ID: + String id = uri.getPathSegments().get(0); + File f = new File(mDataPath, id); + if (f.exists() || MY_AVATAR_ID.equals(f.getName())) + c.newRow().add(f.getName()).add(f.getAbsolutePath()); + break; + default: + Log.w(TAG, "Unsupported uri for query match = " + match); + } + if (c != null) + c.setNotificationUri(getContext().getContentResolver(), uri); + return c; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + int match = URIMATCHER.match(uri); + String id = null; + switch (match) { + case AVATAR_ID: + id = uri.getPathSegments().get(0); + break; + default: + Log.w(TAG, "Unsupported uri for query match = " + match); + } + + if (id == null) + return 0; + + File f = new File(mDataPath, id); + try { + f.createNewFile(); + getContext().getContentResolver().notifyChange(uri, null); + return 1; + } catch (IOException e) { + Log.e(TAG, "Error while creating file", e); + } + return 0; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + int res = 0; + boolean all = false; + String id = null; + int match = URIMATCHER.match(uri); + switch (match) { + case AVATAR_ID: + id = uri.getPathSegments().get(0); + break; + case AVATAR: + all = true; + break; + default: + Log.w(TAG, "Unsupported uri for query match = " + match); + } + File[] list = null; + if (id != null) { + list = new File[] {new File(mDataPath, id) }; + } else if (all) { + list = new File(mDataPath).listFiles(); + } + + if (list == null) + return res; + for (File data : list) { + if (data.exists() && data.delete()) + res++; + } + if (res > 0) + getContext().getContentResolver().notifyChange(uri, null); + return res; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + int match = URIMATCHER.match(uri); + String id = null; + Uri result = null; + switch (match) { + case AVATAR: + id = values.getAsString(Columns.ID); + result = Uri.withAppendedPath(uri, id); + break; + case AVATAR_ID: + id = uri.getPathSegments().get(0); + result = uri; + break; + default: + Log.w(TAG, "Unsupported uri for query match = " + match); + } + if (id == null) + return null; + + File f = new File(mDataPath, id); + try { + f.createNewFile(); + if (result != null) + getContext().getContentResolver().notifyChange(result, null); + return result; + } catch (IOException e) { + Log.e(TAG, "Error while creating file", e); + } + return null; + } + + @Override + public String getType(Uri uri) { + int match = URIMATCHER.match(uri); + switch (match) { + case AVATAR: + return CONTENT_TYPE; + case AVATAR_ID: + return CONTENT_ITEM_TYPE; + default: + Log.w(TAG, "Unsupported uri for query match = " + match); + } + return null; + } + + /** + * The differents columns available in the AvatarProvider. + */ + public interface Columns { + + /** The id of the avatar. + * type: string */ + String ID = "_id"; + + /** The path of the avatar file. + * type: string + * This field is readonly */ + String DATA = "_data"; + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/providers/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/providers/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,49 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ + + +/** + * ContentProviders for Beem. + */ +package com.beem.project.beem.providers; diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/BeemAvatarCache.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/BeemAvatarCache.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,136 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import android.content.ContentResolver; +import android.content.Context; + +import android.database.Cursor; + +import android.net.Uri; + +import com.beem.project.beem.providers.AvatarProvider; +import com.beem.project.beem.smack.avatar.AvatarCache; + + +/** + * An implementation of an AvatarCache which store the data of the filesystem. + */ +public class BeemAvatarCache implements AvatarCache { + + private static final String TAG = BeemAvatarCache.class.getSimpleName(); + + private Context mContext; + private ContentResolver mContentResolver; + + /** + * Create a BeemAvatarCache. + * + * @param ctx The android context of the cache. + */ + public BeemAvatarCache(final Context ctx) { + mContext = ctx; + mContentResolver = mContext.getContentResolver(); + } + + + @Override + public void put(String key, byte[] data) throws IOException { + Uri uri = AvatarProvider.CONTENT_URI.buildUpon().appendPath(key).build(); + mContentResolver.insert(uri, null); + OutputStream os = new BufferedOutputStream(mContentResolver.openOutputStream(uri)); + try { + os.write(data); + } finally { + os.close(); + } + } + + @Override + public void put(String key, InputStream in) throws IOException { + Uri uri = AvatarProvider.CONTENT_URI.buildUpon().appendPath(key).build(); + mContentResolver.insert(uri, null); + OutputStream os = new BufferedOutputStream(mContentResolver.openOutputStream(uri)); + try { + byte[] data = new byte[1024]; + int nbread; + while ((nbread = in.read(data)) != -1) + os.write(data, 0, nbread); + } finally { + in.close(); + os.close(); + } + } + + @Override + public byte[] get(String key) throws IOException { + Uri uri = AvatarProvider.CONTENT_URI.buildUpon().appendPath(key).build(); + InputStream is = new BufferedInputStream(mContentResolver.openInputStream(uri)); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + byte[] data = new byte[1024]; + is.read(data); + bos.write(data); + } finally { + is.close(); + } + return bos.toByteArray(); + } + + @Override + public boolean contains(String key) { + Uri uri = AvatarProvider.CONTENT_URI.buildUpon().appendPath(key).build(); + Cursor c = mContentResolver.query(uri, null, null, null, null); + boolean res = c.getCount() > 0; + c.close(); + return res; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/BeemAvatarManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/BeemAvatarManager.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,170 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ + +package com.beem.project.beem.service; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.Log; + +import com.beem.project.beem.smack.avatar.AvatarManager; +import com.beem.project.beem.smack.avatar.AvatarCache; +import com.beem.project.beem.smack.avatar.AvatarMetadataExtension; + +import java.security.NoSuchAlgorithmException; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import com.beem.project.beem.smack.pep.PepSubManager; +import org.jivesoftware.smack.Connection; + +/** + * An AvatarManager for Beem. + * It allows to publish avatar on the Android platform. + */ +public class BeemAvatarManager extends AvatarManager { + private static final String TAG = BeemAvatarManager.class.getSimpleName(); + private static final int JPEG_QUALITY = 100; + + private Context mContext; + + /** + * Create a BeemAvatarManager. + * + * @param ctx the Android context + * @param con the connection + * @param pepMgr the PepSubManager of the connection + * @param cache the cache which will store the avatars + * @param autoDownload tre to enable auto download of avatars + */ + public BeemAvatarManager(final Context ctx, final Connection con, final PepSubManager pepMgr, + final AvatarCache cache, final boolean autoDownload) { + super(con, pepMgr, cache, autoDownload); + mContext = ctx; + } + + /** + * Publish an avatar. + * + * @param avatarUri the uri of the avatar + * @return true if the avatar was successfully published + */ + public boolean publishAvatar(Uri avatarUri) { + try { + Bitmap bmp = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), avatarUri); + return publishAvatar(bmp); + } catch (IOException e) { + Log.d(TAG, "Error while publishing avatar " + avatarUri, e); + } + return false; + } + + /** + * Publish an avatar. + * This will send the XMPP stanza to enable the publication of an avatar. + * + * @param bitmap the avatar to publish + * @return true on success false otherwise + */ + private boolean publishAvatar(Bitmap bitmap) { + //TODO use the metadata available in the mediastore + AvatarMetadataExtension meta = new AvatarMetadataExtension(); + // Probably a bug on prosody but only the last data sent is kept + // and in beem we retrieve the first info + AvatarMetadataExtension.Info jpeg = publishBitmap(bitmap, Bitmap.CompressFormat.JPEG, JPEG_QUALITY); + // The png format is mandatory for interoperability + AvatarMetadataExtension.Info png = publishBitmap(bitmap, Bitmap.CompressFormat.PNG, JPEG_QUALITY); + if (png == null) + return false; + meta.addInfo(png); + if (jpeg != null) + meta.addInfo(jpeg); + publishAvatarMetaData(png.getId(), meta); + return true; + } + + /** + * Send this bitmap to the avatar data node of the pep server. + * + * @param bmp the avatar bitmap + * @param format the image format to publish this data + * @param quality the compression quality use for JPEG compression + * @return the resulting info associate with this bitmap. null if the operation failed + */ + private AvatarMetadataExtension.Info publishBitmap(Bitmap bmp, Bitmap.CompressFormat format, int quality) { + try { + byte[] data = getBitmapByte(bmp, format, quality); + String dataid = getAvatarId(data); + if (!publishAvatarData(data)) + return null; + String mimetype = "image/png"; + if (Bitmap.CompressFormat.JPEG == format) + mimetype = "image/jpeg"; + AvatarMetadataExtension.Info info = new AvatarMetadataExtension.Info(dataid, mimetype, data.length); + info.setHeight(bmp.getHeight()); + info.setWidth(bmp.getWidth()); + return info; + } catch (NoSuchAlgorithmException ex) { + return null; + } + } + + /** + * Convert the bitmap to a byte array. + * + * @param bitmap the avatar bitmap + * @param format the resulting image format + * @param quality the compression quality use for JPEG compression + * @return the bitmap data or a array of 0 element on error + */ + private byte[] getBitmapByte(Bitmap bitmap, Bitmap.CompressFormat format, int quality) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + if (bitmap.compress(format, quality, bos)) + return bos.toByteArray(); + else + return new byte[0]; + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/BeemChatManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/BeemChatManager.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,410 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.app.PendingIntent; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Environment; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.preference.PreferenceManager; +import android.support.v4.app.NotificationCompat; +import android.util.Log; + +import com.beem.project.beem.BeemApplication; +import com.beem.project.beem.BeemService; +import com.beem.project.beem.R; +import com.beem.project.beem.providers.AvatarProvider; +import com.beem.project.beem.service.aidl.IChat; +import com.beem.project.beem.service.aidl.IChatManager; +import com.beem.project.beem.service.aidl.IChatManagerListener; +import com.beem.project.beem.service.aidl.IMessageListener; +import com.beem.project.beem.service.aidl.IRoster; +import com.beem.project.beem.utils.Status; + +import net.java.otr4j.OtrException; + +import org.jivesoftware.smack.Chat; +import org.jivesoftware.smack.ChatManager; +import org.jivesoftware.smack.ChatManagerListener; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterListener; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; + +/** + * An adapter for smack's ChatManager. This class provides functionnality to handle chats. + * @author darisk + */ +public class BeemChatManager extends IChatManager.Stub { + + private static final String TAG = "BeemChatManager"; + private final ChatManager mAdaptee; + private final Map mChats = new HashMap(); + private final ChatListener mChatListener = new ChatListener(); + private final RemoteCallbackList mRemoteChatCreationListeners = + new RemoteCallbackList(); + private final BeemService mService; + private final Roster mRoster; + private final ChatRosterListener mChatRosterListn = new ChatRosterListener(); + + /** + * Constructor. + * @param chatManager the smack ChatManager to adapt + * @param service the service which runs the chat manager + * @param roster roster used to get presences changes + */ + public BeemChatManager(final ChatManager chatManager, final BeemService service, final Roster roster) { + mService = service; + mAdaptee = chatManager; + mRoster = roster; + mRoster.addRosterListener(mChatRosterListn); + mAdaptee.addChatListener(mChatListener); + } + + @Override + public void addChatCreationListener(IChatManagerListener listener) throws RemoteException { + if (listener != null) + mRemoteChatCreationListeners.register(listener); + } + + /** + * Create a chat session. + * @param contact the contact you want to chat with + * @param listener listener to use for chat events on this chat session + * @return the chat session + */ + @Override + public IChat createChat(Contact contact, IMessageListener listener) { + String jid = contact.getJIDWithRes(); + return createChat(jid, listener); + } + + /** + * Create a chat session. + * @param jid the jid of the contact you want to chat with + * @param listener listener to use for chat events on this chat session + * @return the chat session + */ + public IChat createChat(String jid, IMessageListener listener) { + String key = jid; + ChatAdapter result; + if (mChats.containsKey(key)) { + result = mChats.get(key); + result.addMessageListener(listener); + return result; + } + Chat c = mAdaptee.createChat(key, null); + // maybe a little probleme of thread synchronization + // if so use an HashTable instead of a HashMap for mChats + result = getChat(c); + result.addMessageListener(listener); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public void destroyChat(IChat chat) throws RemoteException { + // Can't remove it. otherwise we will lose all futur message in this chat + // chat.removeMessageListener(mChatListener); + if (chat == null) + return; + deleteChatNotification(chat); + mChats.remove(chat.getParticipant().getJID()); + } + + /** + * {@inheritDoc} + */ + @Override + public void deleteChatNotification(IChat chat) { + try { + mService.deleteNotification(chat.getParticipant().getJID().hashCode()); + } catch (RemoteException e) { + Log.v(TAG, "Remote exception ", e); + } + } + + /** + * Get an existing ChatAdapter or create it if necessary. + * @param chat The real instance of smack chat + * @return a chat adapter register in the manager + */ + private ChatAdapter getChat(Chat chat) { + String key = chat.getParticipant(); + if (mChats.containsKey(key)) { + return mChats.get(key); + } + ChatAdapter res = new ChatAdapter(chat); + boolean history = PreferenceManager.getDefaultSharedPreferences(mService.getBaseContext()).getBoolean( + "settings_key_history", false); + String accountUser = PreferenceManager.getDefaultSharedPreferences(mService.getBaseContext()).getString( + BeemApplication.ACCOUNT_USERNAME_KEY, ""); + String historyPath = PreferenceManager.getDefaultSharedPreferences(mService.getBaseContext()).getString( + BeemApplication.CHAT_HISTORY_KEY, ""); + if ("".equals(historyPath)) historyPath = "/Android/data/com.beem.project.beem/chat/"; + res.setHistory(history); + res.setAccountUser(accountUser); + res.listenOtrSession(); + res.setHistoryPath(new File(Environment.getExternalStorageDirectory(), historyPath)); + Log.d(TAG, "getChat put " + key); + mChats.put(key, res); + return res; + } + + @Override + public ChatAdapter getChat(Contact contact) { + String key = contact.getJIDWithRes(); + return mChats.get(key); + } + + /** + * This methods permits to retrieve the list of contacts who have an opened chat session with us. + * @return An List containing Contact instances. + * @throws RemoteException If a Binder remote-invocation error occurred. + */ + public List getOpenedChatList() throws RemoteException { + List openedChats = new ArrayList(); + IRoster r = mService.getBind().getRoster(); + for (ChatAdapter chat : mChats.values()) { + if (chat.getMessages().size() > 0) { + Contact t = r.getContact(chat.getParticipant().getJID()); + if (t == null) + t = new Contact(chat.getParticipant().getJID()); + openedChats.add(t); + } + } + return openedChats; + } + + /** + * {@inheritDoc} + */ + @Override + public void removeChatCreationListener(IChatManagerListener listener) throws RemoteException { + if (listener != null) + mRemoteChatCreationListeners.unregister(listener); + } + + /** + * A listener for all the chat creation event that happens on the connection. + * @author darisk + */ + private class ChatListener extends IMessageListener.Stub implements ChatManagerListener { + + /** + * Constructor. + */ + public ChatListener() { + } + + /** + * {@inheritDoc} + */ + @Override + public void chatCreated(Chat chat, boolean locally) { + IChat newchat = getChat(chat); + Log.d(TAG, "Chat" + chat.toString() + " created locally " + locally + " with " + chat.getParticipant()); + try { + newchat.addMessageListener(mChatListener); + final int n = mRemoteChatCreationListeners.beginBroadcast(); + + for (int i = 0; i < n; i++) { + IChatManagerListener listener = mRemoteChatCreationListeners.getBroadcastItem(i); + listener.chatCreated(newchat, locally); + } + mRemoteChatCreationListeners.finishBroadcast(); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the + // dead listeners. + Log.w(TAG, " Error while triggering remote connection listeners in chat creation", e); + } + } + + /** + * Create the PendingIntent to launch our activity if the user select this chat notification. + * @param chat A ChatAdapter instance + * @return A Chat activity PendingIntent + */ + private PendingIntent makeChatIntent(IChat chat) { + Intent chatIntent = new Intent(mService, com.beem.project.beem.ui.Chat.class); + chatIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP + | Intent.FLAG_ACTIVITY_NEW_TASK); + try { + chatIntent.setData(chat.getParticipant().toUri()); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + PendingIntent contentIntent = PendingIntent.getActivity(mService, 0, chatIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + return contentIntent; + } + + /** + * Set a notification of a new chat. + * @param chat The chat to access by the notification + * @param msgBody the body of the new message + */ + private void notifyNewChat(IChat chat, String msgBody) { + NotificationCompat.Builder notif = new NotificationCompat.Builder(mService); + try { + String contactJid = chat.getParticipant().getJID(); + Contact c = mService.getBind().getRoster().getContact(contactJid); + String contactName = contactJid; + if (c != null) { + contactName = c.getName(); + Bitmap avatar = getAvatar(c); + notif.setLargeIcon(avatar); + } + notif.setTicker(contactName).setContentTitle(contactName); + notif.setContentText(msgBody); + notif.setSmallIcon(R.drawable.beem_status_icon_gray); + notif.setNumber(chat.getUnreadMessageCount()); + notif.setContentIntent(makeChatIntent(chat)); + notif.setAutoCancel(true).setWhen(System.currentTimeMillis()); + mService.sendNotification(chat.getParticipant().getJID().hashCode(), notif.getNotification()); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + } + + /** + * Get the avatar of a contact. + * @param c the contact + * @return the avatar of c or null if avatar is not defined + */ + private Bitmap getAvatar(Contact c) { + String id = c.getAvatarId(); + if (id == null) + id = ""; + Uri uri = AvatarProvider.CONTENT_URI.buildUpon().appendPath(id).build(); + try { + InputStream in = mService.getContentResolver().openInputStream(uri); + return BitmapFactory.decodeStream(in); + } catch (FileNotFoundException e) { + Log.d(TAG, "Error loading avatar id: " + id, e); + return null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void processMessage(final IChat chat, Message message) { + try { + String body = message.getBody(); + if (!chat.isOpen() && body != null) { + if (chat instanceof ChatAdapter) { + mChats.put(chat.getParticipant().getJID(), (ChatAdapter) chat); + } + notifyNewChat(chat, body); + } + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + } + + @Override + public void stateChanged(final IChat chat) { + } + + @Override + public void otrStateChanged(String otrState) throws RemoteException { + // TODO Auto-generated method stub + + } + } + + /** + * implement a roster listener, is used to detect and close otr chats. + * @author nikita + * + */ + private class ChatRosterListener implements RosterListener { + + @Override + public void entriesAdded(Collection arg0) { + } + + @Override + public void entriesDeleted(Collection arg0) { + } + + @Override + public void entriesUpdated(Collection arg0) { + } + + @Override + public void presenceChanged(Presence presence) { + String key = StringUtils.parseBareAddress(presence.getFrom()); + if (!mChats.containsKey(key)) { + return; + } + + if (Status.getStatusFromPresence(presence) >= Status.CONTACT_STATUS_DISCONNECT) { + try { + mChats.get(key).localEndOtrSession(); + } catch (OtrException e) { + e.printStackTrace(); + } + } + } + + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/ChatAdapter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/ChatAdapter.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,522 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import android.os.Environment; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Log; + +import com.beem.project.beem.otr.BeemOtrManager; +import com.beem.project.beem.service.aidl.IChat; +import com.beem.project.beem.service.aidl.IMessageListener; + +import net.java.otr4j.OtrException; +import net.java.otr4j.session.SessionID; + +import org.jivesoftware.smack.Chat; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.ChatState; +import org.jivesoftware.smackx.ChatStateListener; + +/** + * An adapter for smack's Chat class. + * @author darisk + */ +public class ChatAdapter extends IChat.Stub { + private static final int HISTORY_MAX_SIZE = 50; + private static final String TAG = "ChatAdapter"; + private static final String PROTOCOL = "XMPP"; + + private final Chat mAdaptee; + private final Contact mParticipant; + private String mState; + private boolean mIsOpen; + private final List mMessages; + private final RemoteCallbackList mRemoteListeners = new RemoteCallbackList(); + private final MsgListener mMsgListener = new MsgListener(); + private SessionID mOtrSessionId; + private boolean mIsHistory; + private File mHistoryPath; + private String mAccountUser; + private int mUnreadMsgCount; + + /** + * Constructor. + * @param chat The chat to adapt + */ + public ChatAdapter(final Chat chat) { + mAdaptee = chat; + mParticipant = new Contact(chat.getParticipant()); + mMessages = new LinkedList(); + mAdaptee.addMessageListener(mMsgListener); + } + + /** + * {@inheritDoc} + */ + @Override + public Contact getParticipant() throws RemoteException { + return mParticipant; + } + + /** + * {@inheritDoc} + */ + @Override + public void sendMessage(com.beem.project.beem.service.Message message) throws RemoteException { + com.beem.project.beem.service.Message encrypted = otrEncryptMessage(message); + if (encrypted != null) { + transferMessage(encrypted); + } else { + transferMessage(message); + } + addMessage(message); + } + + /** + * private method for sending message. + * @param message the message to send + */ + private void transferMessage(com.beem.project.beem.service.Message message) { + org.jivesoftware.smack.packet.Message send = new org.jivesoftware.smack.packet.Message(); + String msgBody = message.getBody(); + send.setTo(message.getTo()); + Log.w(TAG, "message to " + message.getTo()); + send.setBody(msgBody); + + send.setThread(message.getThread()); + send.setSubject(message.getSubject()); + send.setType(org.jivesoftware.smack.packet.Message.Type.chat); + // TODO gerer les messages contenant des XMPPError + // send.set + try { + mAdaptee.sendMessage(send); + } catch (XMPPException e) { + e.printStackTrace(); + } + } + + /** + * send message. + * @param msg to send. + */ + public void injectMessage(String msg) { + Message msgToSend = new Message(mParticipant.getJIDWithRes(), Message.MSG_TYPE_CHAT); + msgToSend.setBody(msg); + transferMessage(msgToSend); + } + + /** + * {@inheritDoc} + */ + @Override + public void addMessageListener(IMessageListener listen) { + if (listen != null) + mRemoteListeners.register(listen); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeMessageListener(IMessageListener listen) { + if (listen != null) { + mRemoteListeners.unregister(listen); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getState() throws RemoteException { + return mState; + } + + /** + * {@inheritDoc} + */ + @Override + public void setState(String state) throws RemoteException { + mState = state; + } + + /** + * Get the adaptee for the Chat. + * @return The real chat object + */ + public Chat getAdaptee() { + return mAdaptee; + } + + /** + * {@inheritDoc} + */ + @Override + public void setOpen(boolean isOpen) { + this.mIsOpen = isOpen; + if (isOpen) + mUnreadMsgCount = 0; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isOpen() { + return mIsOpen; + } + + /** + * {@inheritDoc} + */ + @Override + public List getMessages() throws RemoteException { + return Collections.unmodifiableList(mMessages); + } + + /** + * Add a message in the chat history. + * @param msg the message to add + */ + private void addMessage(Message msg) { + if (mMessages.size() == HISTORY_MAX_SIZE) + mMessages.remove(0); + mMessages.add(msg); + if (!isOpen()) + mUnreadMsgCount++; + if (!"".equals(msg.getBody()) && msg.getBody() != null) { + logMessage(msg); + } + } + + /** + * Save message in SDCard. + * @param msg the message receive + * @param contactName the name of the contact + */ + public void saveHistory(Message msg, String contactName) { + File path = getHistoryPath(); + File filepath; + if (contactName.equals(msg.getFrom())) + filepath = new File(path, StringUtils.parseBareAddress(contactName)); + else + filepath = new File(path, StringUtils.parseBareAddress(msg.getTo())); + path.mkdirs(); + try { + FileWriter file = new FileWriter(filepath, true); + String log = msg.getTimestamp() + " " + contactName + " " + msg.getBody() + + System.getProperty("line.separator"); + file.write(log); + file.close(); + } catch (IOException e) { + Log.e(TAG, "Error writing chat history", e); + } + } + + /** + * set History enable/disable. + * @param isHisory history state + */ + public void setHistory(boolean isHisory) { + this.mIsHistory = isHisory; + } + + /** + * get History state. + * @return mIsHistory + */ + public boolean getHistory() { + return mIsHistory; + } + + /** + * Set Account user name. + * @param accountUser user name + */ + public void setAccountUser(String accountUser) { + mAccountUser = accountUser; + } + + /** + * get Account user name. + * @return mAccountUser + */ + public String getAccountUser() { + return mAccountUser; + } + + /** + * set History path. + * @param historyPath history path + */ + public void setHistoryPath(File historyPath) { + this.mHistoryPath = historyPath; + } + + /** + * get History path. + * @return mHistoryPath; + */ + public File getHistoryPath() { + return mHistoryPath; + } + + /** + * log a message. + * @param message message to log + */ + private void logMessage(com.beem.project.beem.service.Message message) { + String state = Environment.getExternalStorageState(); + if (mIsHistory && Environment.MEDIA_MOUNTED.equals(state)) + saveHistory(message, mAccountUser); + + } + + /** + * encrypt a message with an otr session. + * @param unencrypted message with cleartext body + * @return message with encrypted body + */ + private com.beem.project.beem.service.Message otrEncryptMessage(com.beem.project.beem.service.Message unencrypted) { + + if (mOtrSessionId != null && unencrypted != null && unencrypted.getBody() != null) { + try { + String body = BeemOtrManager.getInstance().getOtrManager() + .transformSending(mOtrSessionId, unencrypted.getBody()); + Message result = new Message(unencrypted.getTo(), unencrypted.getType()); + result.setBody(body); + return result; + } catch (OtrException e) { + Log.e(TAG, "OTR: Unable to encrypt message", e); + } + } + return null; + } + + /** + * This method is executed when the otr session status change. + * @param otrState the new state of otr session. + */ + public void otrStateChanged(final String otrState) { + Message m = new Message(null, Message.MSG_TYPE_INFO); + m.setBody(otrState); + addMessage(m); + final int n = mRemoteListeners.beginBroadcast(); + + for (int i = 0; i < n; i++) { + IMessageListener listener = mRemoteListeners.getBroadcastItem(i); + try { + listener.otrStateChanged(otrState); + } catch (RemoteException e) { + Log.w(TAG, e.getMessage()); + } + } + mRemoteListeners.finishBroadcast(); + } + + @Override + public void startOtrSession() throws RemoteException { + if (mOtrSessionId == null) { + mOtrSessionId = new SessionID(mAccountUser, mParticipant.getJIDWithRes(), PROTOCOL); + BeemOtrManager.getInstance().addChat(mOtrSessionId, this); + } + + try { + BeemOtrManager.getInstance().getOtrManager().startSession(mOtrSessionId); + } catch (OtrException e) { + mOtrSessionId = null; + e.printStackTrace(); + throw new RemoteException(); + } + } + + @Override + public void endOtrSession() throws RemoteException { + try { + localEndOtrSession(); + } catch (OtrException e) { + e.printStackTrace(); + throw new RemoteException(); + } + } + + /** + * end an Otr session. + * @return false if something bad happened. + * @throws OtrException an exception from otr + */ + public boolean localEndOtrSession() throws OtrException { + if (mOtrSessionId == null) + return true; + + BeemOtrManager.getInstance().getOtrManager().endSession(mOtrSessionId); + BeemOtrManager.getInstance().removeChat(mOtrSessionId); + mOtrSessionId = null; + listenOtrSession(); + return true; + } + + /** + * Start listenning to an OTR session. + */ + public void listenOtrSession() { + if (mOtrSessionId != null) + return; + + mOtrSessionId = new SessionID(mAccountUser, mParticipant.getJIDWithRes(), PROTOCOL); + BeemOtrManager.getInstance().addChat(mOtrSessionId, this); + //OtrEngineImpl will make a call to "this.getSession(sessionID)" which will instantiate our session. + BeemOtrManager.getInstance().getOtrManager().getSessionStatus(mOtrSessionId); + } + + @Override + public String getLocalOtrFingerprint() throws RemoteException { + if (mOtrSessionId == null) + return null; + + return BeemOtrManager.getInstance().getLocalFingerprint(mOtrSessionId); + } + + @Override + public String getRemoteOtrFingerprint() throws RemoteException { + if (mOtrSessionId == null) + return null; + + return BeemOtrManager.getInstance().getRemoteFingerprint(mOtrSessionId); + } + + @Override + public void verifyRemoteFingerprint(boolean ok) { + if (mOtrSessionId != null) { + if (ok) + BeemOtrManager.getInstance().verifyRemoteFingerprint(mOtrSessionId); + else + BeemOtrManager.getInstance().unverifyRemoteFingerprint(mOtrSessionId); + } + } + + @Override + public String getOtrStatus() throws RemoteException { + if (mOtrSessionId == null) + return null; + return BeemOtrManager.getInstance().getOtrManager().getSessionStatus(mOtrSessionId).toString(); + } + + @Override + public int getUnreadMessageCount() throws RemoteException { + return mUnreadMsgCount; + } + + /** + * Listener. + */ + private class MsgListener implements ChatStateListener { + /** + * Constructor. + */ + public MsgListener() { + } + + @Override + public void processMessage(Chat chat, org.jivesoftware.smack.packet.Message message) { + Message msg = new Message(message); + Log.d(TAG, "new msg " + msg.getBody()); + String body; + + if (mOtrSessionId != null) { + try { + body = BeemOtrManager.getInstance().getOtrManager() + .transformReceiving(mOtrSessionId, msg.getBody()); + msg.setBody(body); + } catch (OtrException e) { + Log.w(TAG, "Unable to decrypt OTR message", e); + } + } + //TODO add que les message pas de type errors + ChatAdapter.this.addMessage(msg); + final int n = mRemoteListeners.beginBroadcast(); + for (int i = 0; i < n; i++) { + IMessageListener listener = mRemoteListeners.getBroadcastItem(i); + try { + if (listener != null) + listener.processMessage(ChatAdapter.this, msg); + } catch (RemoteException e) { + Log.w(TAG, "Error while diffusing message to listener", e); + } + } + mRemoteListeners.finishBroadcast(); + } + + /** + * {@inheritDoc} + */ + @Override + public void stateChanged(Chat chat, ChatState state) { + mState = state.name(); + final int n = mRemoteListeners.beginBroadcast(); + + for (int i = 0; i < n; i++) { + IMessageListener listener = mRemoteListeners.getBroadcastItem(i); + try { + listener.stateChanged(ChatAdapter.this); + } catch (RemoteException e) { + Log.w(TAG, e.getMessage()); + } + } + mRemoteListeners.finishBroadcast(); + } + + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/Contact.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/Contact.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,461 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.jivesoftware.smack.RosterGroup; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import com.beem.project.beem.utils.Status; + +/** + * This class contains informations on a jabber contact. + * @author darisk + */ +public class Contact implements Parcelable { + + /** Parcelable.Creator needs by Android. */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public Contact createFromParcel(Parcel source) { + return new Contact(source); + } + + @Override + public Contact[] newArray(int size) { + return new Contact[size]; + } + }; + + private int mID; + private int mStatus; + private final String mJID; + private String mSelectedRes; + private String mMsgState; + private List mRes; + private final List mGroups = new ArrayList(); + private String mName; + private String mAvatarId; + + /** + * Construct a contact from a parcel. + * @param in parcel to use for construction + */ + private Contact(final Parcel in) { + mID = in.readInt(); + mStatus = in.readInt(); + mJID = in.readString(); + mSelectedRes = in.readString(); + mName = in.readString(); + mMsgState = in.readString(); + mAvatarId = in.readString(); + mRes = new ArrayList(); + in.readStringList(mRes); + in.readStringList(mGroups); + } + + /** + * Constructor. + * @param jid JID of the contact + */ + public Contact(final String jid) { + mJID = StringUtils.parseBareAddress(jid); + mName = mJID; + mStatus = Status.CONTACT_STATUS_DISCONNECT; + mMsgState = null; + mRes = new ArrayList(); + String res = StringUtils.parseResource(jid); + mSelectedRes = res; + if (!"".equals(res)) + mRes.add(res); + } + + /** + * Create a contact from a Uri. + * @param uri an uri for the contact + * @throws IllegalArgumentException if it is not a xmpp uri + */ + public Contact(final Uri uri) { + if (!"xmpp".equals(uri.getScheme())) + throw new IllegalArgumentException(); + String enduri = uri.getEncodedSchemeSpecificPart(); + mJID = StringUtils.parseBareAddress(enduri); + mName = mJID; + mStatus = Status.CONTACT_STATUS_DISCONNECT; + mMsgState = null; + mRes = new ArrayList(); + String res = StringUtils.parseResource(enduri); + mSelectedRes = res; + mRes.add(res); + } + + /** + * Make an xmpp uri for a spcific jid. + * + * @param jid the jid to represent as an uri + * @return an uri representing this jid. + */ + public static Uri makeXmppUri(String jid) { + StringBuilder build = new StringBuilder("xmpp:"); + String name = StringUtils.parseName(jid); + build.append(name); + if (!"".equals(name)) + build.append('@'); + build.append(StringUtils.parseServer(jid)); + String resource = StringUtils.parseResource(jid); + if (!"".equals(resource)) { + build.append('/'); + build.append(resource); + } + Uri u = Uri.parse(build.toString()); + return u; + } + + /** + * {@inheritDoc} + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mID); + dest.writeInt(mStatus); + dest.writeString(mJID); + dest.writeString(mSelectedRes); + dest.writeString(mName); + dest.writeString(mMsgState); + dest.writeString(mAvatarId); + dest.writeStringList(getMRes()); + dest.writeStringList(getGroups()); + } + + /** + * Add a group for the contact. + * @param group the group + */ + public void addGroup(String group) { + if (!mGroups.contains(group)) + mGroups.add(group); + } + + /** + * Remove the contact from a group. + * @param group the group to delete the contact from. + */ + public void delGroup(String group) { + mGroups.remove(group); + } + + /** + * Add a resource for this contact. + * @param res the resource to add + */ + public void addRes(String res) { + if (!mRes.contains(res)) + mRes.add(res); + } + + /** + * Delete a resource for this contact. + * @param res the resource de delete + */ + public void delRes(String res) { + mRes.remove(res); + } + + /** + * {@inheritDoc} + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Get the groups the contact is in. + * @return the mGroups + */ + public List getGroups() { + return mGroups; + } + + /** + * Get the id of the contact on the phone contact list. + * @return the mID + */ + public int getID() { + return mID; + } + + /** + * Get the Jabber ID of the contact. + * @return the Jabber ID + */ + public String getJID() { + return mJID; + } + + /** + * Get selected resource. + * @return the selected resource. + */ + public String getSelectedRes() { + return mSelectedRes; + } + + /** + * Get the list of resource for the contact. + * @return the mRes + */ + public List getMRes() { + return mRes; + } + + /** + * Get the message status of the contact. + * @return the message status of the contact. + */ + public String getMsgState() { + return mMsgState; + } + + /** + * Get the name of the contact. + * @return the mName + */ + public String getName() { + return mName; + } + + /** + * Get the status of the contact. + * @return the mStatus + */ + public int getStatus() { + return mStatus; + } + + /** + * Get the avatar id of the contact. + * + * @return the avatar id or null if there is not + */ + public String getAvatarId() { + return mAvatarId; + } + + /** + * Set the groups the contact is in. + * @param groups list of groups + */ + public void setGroups(Collection groups) { + this.mGroups.clear(); + for (RosterGroup rosterGroup : groups) { + mGroups.add(rosterGroup.getName()); + } + } + + /** + * Set the groups the contact is in. + * @param groups the mGroups to set + */ + public void setGroups(List groups) { + mGroups.clear(); + mGroups.addAll(groups); + } + + /** + * set the id of te contact on the phone contact list. + * @param mid the mID to set + */ + public void setID(int mid) { + mID = mid; + } + + /** + * Set the avatar id of the contact. + * + * @param avatarId the avatar id + */ + public void setAvatarId(String avatarId) { + mAvatarId = avatarId; + } + + /** + * Set the resource of the contact. + * @param resource to set. + */ + public void setSelectedRes(String resource) { + mSelectedRes = resource; + } + + /** + * Set a list of resource for the contact. + * @param mRes the mRes to set + */ + public void setMRes(List mRes) { + this.mRes = mRes; + } + + /** + * Set the message status of the contact. + * @param msgState the message status of the contact to set + */ + public void setMsgState(String msgState) { + mMsgState = msgState; + } + + /** + * Set the name of the contact. + * @param name the mName to set + */ + public void setName(String name) { + if (name == null || "".equals(name)) { + this.mName = this.mJID; + this.mName = StringUtils.parseName(this.mName); + if (this.mName == null || "".equals(this.mName)) + this.mName = this.mJID; + } else { + this.mName = name; + } + } + + /** + * Set the status of the contact. + * @param status the mStatus to set + */ + public void setStatus(int status) { + mStatus = status; + } + + /** + * Set the status of the contact using a presence packet. + * @param presence the presence containing status + */ + public void setStatus(Presence presence) { + mStatus = Status.getStatusFromPresence(presence); + mMsgState = presence.getStatus(); + } + + /** + * Set status for the contact. + * @param presence The presence packet which contains the status + */ + public void setStatus(PresenceAdapter presence) { + mStatus = presence.getStatus(); + mMsgState = presence.getStatusText(); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + if (mJID != null) + return mJID + "/[" + mRes + "]"; + return super.toString(); + } + + /** + * Get a URI to access the contact. + * @return the URI + */ + public Uri toUri() { + return makeXmppUri(mJID); + } + + /** + * Get a URI to access the specific contact on this resource. + * @param resource the resource of the contact + * @return the URI + */ + public Uri toUri(String resource) { + StringBuilder build = new StringBuilder("xmpp:"); + String name = StringUtils.parseName(mJID); + build.append(name); + if (!"".equals(name)) + build.append('@'); + build.append(StringUtils.parseServer(mJID)); + if (!"".equals(resource)) { + build.append('/'); + build.append(resource); + } + Uri u = Uri.parse(build.toString()); + return u; + } + + /** + * Get a JID to access the specific contact on this resource. + * @return the JID. + */ + public String getJIDWithRes() { + StringBuilder build = new StringBuilder(mJID); + if (!"".equals(mSelectedRes)) + build.append('/').append(mSelectedRes); + return build.toString(); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Contact)) + return false; + if (other == this) + return true; + Contact c = (Contact) other; + return c.getJID().equals(getJID()); + } + + @Override + public int hashCode() { + return mJID.hashCode(); + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/LoginAsyncTask.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/LoginAsyncTask.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,134 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +import android.os.AsyncTask; +import android.os.RemoteException; +import android.util.Log; +import com.beem.project.beem.service.aidl.IXmppConnection; +import com.beem.project.beem.service.aidl.IXmppFacade; + +/** + * This is an asynchronous task that will launch a connection to the XMPP server. + * @see android.os.AsyncTask + * @author Da Risk + */ +public class LoginAsyncTask extends AsyncTask { + + /** + * State of a running connection. + */ + public static final int STATE_CONNECTION_RUNNING = 0; + /** + * State of an already connected connection but authentication is running. + */ + public static final int STATE_LOGIN_RUNNING = 1; + /** + * State of a connected and authenticated succesfully. + */ + public static final int STATE_LOGIN_SUCCESS = 2; + /** + * State of a connected but failed authentication. + */ + public static final int STATE_LOGIN_FAILED = 3; + + private static final String TAG = "BeemLoginTask"; + + private IXmppConnection mConnection; + private String mErrorMessage; + + /** + * Constructor. + */ + public LoginAsyncTask() { + } + + @Override + protected Boolean doInBackground(IXmppFacade... params) { + boolean result = true; + IXmppFacade facade = params[0]; + try { + publishProgress(STATE_CONNECTION_RUNNING); + mConnection = facade.createConnection(); + if (!mConnection.connect()) { + mErrorMessage = mConnection.getErrorMessage(); + return false; + } + publishProgress(STATE_LOGIN_RUNNING); + + if (!mConnection.login()) { + mErrorMessage = mConnection.getErrorMessage(); + publishProgress(STATE_LOGIN_FAILED); + return false; + } + publishProgress(STATE_LOGIN_SUCCESS); + } catch (RemoteException e) { + mErrorMessage = "Exception during connection :" + e; + result = false; + } + return result; + } + + /** + * Make sur to call the parent method when overriding this method. + */ + @Override + protected void onCancelled() { + try { + if (mConnection != null && mConnection.isAuthentificated()) { + mConnection.disconnect(); + } + } catch (RemoteException e) { + Log.d(TAG, "Remote exception", e); + } + } + + /** + * Get the error Message. + * @return the error message. null if no error + */ + public String getErrorMessage() { + return mErrorMessage; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/Message.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/Message.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,322 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smackx.packet.DelayInformation; +import org.jivesoftware.smack.packet.PacketExtension; + +import android.os.Parcel; +import android.os.Parcelable; +import java.util.Date; + +/** + * This class represents a instant message. + * @author darisk + */ +public class Message implements Parcelable { + + /** Normal message type. Theese messages are like an email, with subject. */ + public static final int MSG_TYPE_NORMAL = 100; + + /** Chat message type. */ + public static final int MSG_TYPE_CHAT = 200; + + /** Group chat message type. */ + public static final int MSG_TYPE_GROUP_CHAT = 300; + + /** Error message type. */ + public static final int MSG_TYPE_ERROR = 400; + + /** Informational message type. */ + public static final int MSG_TYPE_INFO = 500; + + /** Parcelable.Creator needs by Android. */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public Message createFromParcel(Parcel source) { + return new Message(source); + } + + @Override + public Message[] newArray(int size) { + return new Message[size]; + } + }; + + private int mType; + private String mBody; + private String mSubject; + private String mTo; + private String mFrom; + private String mThread; + private Date mTimestamp; + + // TODO ajouter l'erreur + + /** + * Constructor. + * @param to the destinataire of the message + * @param type the message type + */ + public Message(final String to, final int type) { + mTo = to; + mType = type; + mBody = ""; + mSubject = ""; + mThread = ""; + mFrom = null; + mTimestamp = new Date(); + } + + /** + * Constructor a message of type chat. + * @param to the destinataire of the message + */ + public Message(final String to) { + this(to, MSG_TYPE_CHAT); + } + + /** + * Construct a message from a smack message packet. + * @param smackMsg Smack message packet + */ + public Message(final org.jivesoftware.smack.packet.Message smackMsg) { + this(smackMsg.getTo()); + switch (smackMsg.getType()) { + case chat: + mType = MSG_TYPE_CHAT; + break; + case groupchat: + mType = MSG_TYPE_GROUP_CHAT; + break; + case normal: + mType = MSG_TYPE_NORMAL; + break; + // TODO gerer les message de type error + // this a little work around waiting for a better handling of error + // messages + case error: + mType = MSG_TYPE_ERROR; + break; + default: + mType = MSG_TYPE_NORMAL; + break; + } + this.mFrom = smackMsg.getFrom(); + //TODO better handling of error messages + if (mType == MSG_TYPE_ERROR) { + XMPPError er = smackMsg.getError(); + String msg = er.getMessage(); + if (msg != null) + mBody = msg; + else + mBody = er.getCondition(); + } else { + mBody = smackMsg.getBody(); + mSubject = smackMsg.getSubject(); + mThread = smackMsg.getThread(); + } + PacketExtension pTime = smackMsg.getExtension("delay", "urn:xmpp:delay"); + if (pTime instanceof DelayInformation) { + mTimestamp = ((DelayInformation) pTime).getStamp(); + } else { + mTimestamp = new Date(); + } + } + + /** + * Construct a message from a parcel. + * @param in parcel to use for construction + */ + private Message(final Parcel in) { + mType = in.readInt(); + mTo = in.readString(); + mBody = in.readString(); + mSubject = in.readString(); + mThread = in.readString(); + mFrom = in.readString(); + mTimestamp = new Date(in.readLong()); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + // TODO Auto-generated method stub + dest.writeInt(mType); + dest.writeString(mTo); + dest.writeString(mBody); + dest.writeString(mSubject); + dest.writeString(mThread); + dest.writeString(mFrom); + dest.writeLong(mTimestamp.getTime()); + } + + /** + * Get the type of the message. + * @return the type of the message. + */ + public int getType() { + return mType; + } + + /** + * Set the type of the message. + * @param type the type to set + */ + public void setType(int type) { + mType = type; + } + + /** + * Get the body of the message. + * @return the Body of the message + */ + public String getBody() { + return mBody; + } + + /** + * Set the body of the message. + * @param body the body to set + */ + public void setBody(String body) { + mBody = body; + } + + /** + * Get the subject of the message. + * @return the subject + */ + public String getSubject() { + return mSubject; + } + + /** + * Set the subject of the message. + * @param subject the subject to set + */ + public void setSubject(String subject) { + mSubject = subject; + } + + /** + * Get the destinataire of the message. + * @return the destinataire of the message + */ + public String getTo() { + return mTo; + } + + /** + * Set the destinataire of the message. + * @param to the destinataire to set + */ + public void setTo(String to) { + mTo = to; + } + + /** + * Set the from field of the message. + * @param from the mFrom to set + */ + public void setFrom(String from) { + this.mFrom = from; + } + + /** + * Get the from field of the message. + * @return the mFrom + */ + public String getFrom() { + return mFrom; + } + + /** + * Get the thread of the message. + * @return the thread + */ + public String getThread() { + return mThread; + } + + /** + * Set the thread of the message. + * @param thread the thread to set + */ + public void setThread(String thread) { + mThread = thread; + } + + /** + * Set the Date of the message. + * + * @param date date of the message. + */ + public void setTimestamp(Date date) { + mTimestamp = date; + } + + /** + * Get the Date of the message. + * + * @return if it is a delayed message get the date the message was sended. + */ + public Date getTimestamp() { + return mTimestamp; + } + + /** + * {@inheritDoc} + */ + @Override + public int describeContents() { + // TODO Auto-generated method stub + return 0; + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/PresenceAdapter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/PresenceAdapter.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,204 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +import org.jivesoftware.smack.packet.Presence; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.beem.project.beem.utils.PresenceType; +import com.beem.project.beem.utils.Status; + +/** + * this class contain contact presence informations. + * @author nikita + */ +public class PresenceAdapter implements Parcelable { + + /** Parcelable.Creator needs by Android. */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public PresenceAdapter createFromParcel(Parcel source) { + return new PresenceAdapter(source); + } + + @Override + public PresenceAdapter[] newArray(int size) { + return new PresenceAdapter[size]; + } + }; + + private int mType; + private int mStatus; + private String mTo; + private String mFrom; + private String mStatusText; + + /** + * constructor from Parcel. + * @param source parcelable presence. + */ + public PresenceAdapter(final Parcel source) { + mType = source.readInt(); + mStatus = source.readInt(); + mTo = source.readString(); + mFrom = source.readString(); + mStatusText = source.readString(); + } + + /** + * constructor from smack Presence. + * @param presence smack presence. + */ + public PresenceAdapter(final Presence presence) { + mType = PresenceType.getPresenceType(presence); + mStatus = Status.getStatusFromPresence(presence); + mTo = presence.getTo(); + mFrom = presence.getFrom(); + mStatusText = presence.getStatus(); + } + + /* (non-Javadoc) + * @see android.os.Parcelable#describeContents() + */ + @Override + public int describeContents() { + // TODO Auto-generated method stub + return 0; + } + + /** + * mFrom getter. + * @return the mFrom + */ + public String getFrom() { + return mFrom; + } + + /** + * mStatus getter. + * @return the mStatus + */ + public int getStatus() { + return mStatus; + } + + /** + * mStatusText getter. + * @return the mStatusText + */ + public String getStatusText() { + return mStatusText; + } + + /** + * mTo getter. + * @return the mTo + */ + public String getTo() { + return mTo; + } + + /** + * mType getter. + * @return the mType + */ + public int getType() { + return mType; + } + + /** + * mFrom setter. + * @param from the mFrom to set + */ + public void setFrom(final String from) { + this.mFrom = from; + } + + /** + * mStatus setter. + * @param status the mStatus to set + */ + public void setStatus(final int status) { + this.mStatus = status; + } + + /** + * mStatusText setter. + * @param statusText the mStatusText to set + */ + public void setStatusText(final String statusText) { + this.mStatusText = statusText; + } + + /** + * mTo setter. + * @param to the mTo to set + */ + public void setTo(final String to) { + this.mTo = to; + } + + /** + * mType setter. + * @param type the type to set + */ + public void setType(int type) { + this.mType = type; + } + + /* (non-Javadoc) + * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int) + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeInt(mStatus); + dest.writeString(mTo); + dest.writeString(mFrom); + dest.writeString(mStatusText); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/PrivacyListItem.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/PrivacyListItem.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,153 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A simplified version of the Smack PrivacyItem class. + * @author Jean-Manuel Da Silva + */ +public class PrivacyListItem implements Parcelable { + + /** + * Constructor. Needed to implements the Parcelable.Creator interface. Generates instances of PrivacyListItem from a + * Parcel. + */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public PrivacyListItem createFromParcel(Parcel in) { + return new PrivacyListItem(in); + } + + public PrivacyListItem[] newArray(int size) { + return new PrivacyListItem[size]; + } + }; + + private int mType; + private String mValue; + + /** + * Constructor. + */ + public PrivacyListItem() { + } + + /** + * Constructor. Generates instances of PrivacyListItem from a Parcel. + * @param in The Parcel used to initialize object's attributes. + */ + public PrivacyListItem(final Parcel in) { + readFromParcel(in); + } + + /** + * Constructor. + * @param type The type of the item. + * @param value The value of the item. + */ + public PrivacyListItem(final int type, final String value) { + mType = type; + mValue = value; + } + + /** + * {@inheritDoc}. + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Initialize object's attributes from a Parcel. + * @param in The Parcel used to initialize object's attributes. + */ + public void readFromParcel(Parcel in) { + mType = in.readInt(); + mValue = in.readString(); + } + + /** + * {@inheritDoc}. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeString(mValue); + } + + /** + * PrivacyListItem type accessor. + * @return The type of the PrivacyListItem. + */ + public int getType() { + return mType; + } + + /** + * PrivacyListItem value accessor. + * @return The value of the PrivacyListItem. + */ + public String getValue() { + return mValue; + } + + /** + * PrivacyListItem type mutator. + * @param type The type of the PrivacyListItem. + */ + public void setType(final int type) { + mType = type; + } + + /** + * PrivacyListItem value mutator. + * @param value The value of the PrivacyListItem. + */ + public void setValue(final String value) { + mValue = value; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/PrivacyListManagerAdapter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/PrivacyListManagerAdapter.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,358 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +import java.util.ArrayList; +import java.util.List; + +import org.jivesoftware.smack.PrivacyList; +import org.jivesoftware.smack.PrivacyListListener; +import org.jivesoftware.smack.PrivacyListManager; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.PrivacyItem; +import org.jivesoftware.smack.packet.PrivacyItem.PrivacyRule; + +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Log; + +import com.beem.project.beem.service.aidl.IPrivacyListListener; +import com.beem.project.beem.service.aidl.IPrivacyListManager; + +/** + * An adapter for the Smack's PrivacyListManager. + * @author Jean-Manuel Da Silva + */ +public class PrivacyListManagerAdapter extends IPrivacyListManager.Stub { + + /** Class's Tag. */ + public static final String TAG = "PrivacyListManagerAdapter"; + + private final PrivacyListManager mPrivacyListManager; + + private final RemoteCallbackList mPrivacyListListeners = + new RemoteCallbackList(); + private final PrivacyListListenerAdapter mPrivacyListListener = new PrivacyListListenerAdapter(); + + /** + * Constructor. + * @param privacyListManager the privacy list manager + */ + public PrivacyListManagerAdapter(final PrivacyListManager privacyListManager) { + mPrivacyListManager = privacyListManager; + mPrivacyListManager.addListener(mPrivacyListListener); + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IPrivacyListManager#blockUser(java.lang.String, java.lang.String) + */ + @Override + public void blockUser(String listName, String jid) throws RemoteException { + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IPrivacyListManager#createPrivacyList(java.lang.String, java.util.List) + */ + @Override + public void createPrivacyList(String listName, List items) throws RemoteException { + Log.d(TAG, "BEGIN createPrivacyList."); + try { + List privacyItems = new ArrayList(); + + PrivacyItem item = new PrivacyItem(PrivacyItem.Type.subscription.name(), true, 2); + item.setValue(PrivacyRule.SUBSCRIPTION_BOTH); + privacyItems.add(item); + + mPrivacyListManager.createPrivacyList(listName, privacyItems); + } catch (XMPPException e) { + Log.e(TAG, e.getMessage()); + } + Log.d(TAG, "END createPrivacyList."); + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IPrivacyListManager#declineActivePrivacyList() + */ + @Override + public void declineActivePrivacyList() throws RemoteException { + try { + mPrivacyListManager.declineActiveList(); + } catch (XMPPException e) { + Log.e(TAG, e.getMessage()); + } + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IPrivacyListManager#declineDefaultPrivacyList() + */ + @Override + public void declineDefaultPrivacyList() throws RemoteException { + try { + mPrivacyListManager.declineDefaultList(); + } catch (XMPPException e) { + Log.e(TAG, e.getMessage()); + } + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IPrivacyListManager#editPrivacyList(java.lang.String, java.util.List) + */ + @Override + public void editPrivacyList(String listName, List items) throws RemoteException { + Log.d(TAG, "BEGIN editPrivacyList."); + try { + mPrivacyListManager.updatePrivacyList(listName, tranformPrivacyListItemsToPrivacyItems(items)); + } catch (XMPPException e) { + Log.e(TAG, e.getMessage()); + } + Log.d(TAG, "END editPrivacyList."); + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IPrivacyListManager#getActivePrivacyList() + */ + @Override + public String getActivePrivacyList() throws RemoteException { + try { + PrivacyList activePrivacyList = mPrivacyListManager.getActiveList(); + return activePrivacyList.toString(); + } catch (XMPPException e) { + Log.e(TAG, e.getMessage()); + } + return null; + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IPrivacyListManager#getBlockedGroupsByList(java.lang.String) + */ + @Override + public List getBlockedGroupsByList(String listName) throws RemoteException { + List blockedGroups = new ArrayList(); + try { + PrivacyList pL = mPrivacyListManager.getPrivacyList(listName); + for (PrivacyItem pI : pL.getItems()) { + if (pI.getType().equals(PrivacyItem.Type.group) && !pI.isAllow()) + blockedGroups.add(pI.getValue()); + } + } catch (XMPPException e) { + Log.e(TAG, e.getMessage()); + } + return blockedGroups; + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IPrivacyListManager#getBlockedUsersByList(java.lang.String) + */ + @Override + public List getBlockedUsersByList(String listName) throws RemoteException { + List blockedUsers = new ArrayList(); + try { + PrivacyList pL = mPrivacyListManager.getPrivacyList(listName); + for (PrivacyItem pI : pL.getItems()) { + if (pI.getType().equals(PrivacyItem.Type.jid) && !pI.isAllow()) + blockedUsers.add(pI.getValue()); + } + } catch (XMPPException e) { + Log.e(TAG, e.getMessage()); + } + return blockedUsers; + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IPrivacyListManager#getDefaultPrivacyList() + */ + @Override + public String getDefaultPrivacyList() throws RemoteException { + try { + PrivacyList defaultPrivacyList = mPrivacyListManager.getDefaultList(); + return defaultPrivacyList.toString(); + } catch (XMPPException e) { + Log.e(TAG, e.getMessage()); + } + return null; + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IPrivacyListManager#removePrivacyList(java.lang.String) + */ + @Override + public void removePrivacyList(String listName) throws RemoteException { + try { + mPrivacyListManager.deletePrivacyList(listName); + } catch (XMPPException e) { + Log.e(TAG, e.getMessage()); + } + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IPrivacyListManager#setActivePrivacyList(java.lang.String) + */ + @Override + public void setActivePrivacyList(String listName) throws RemoteException { + try { + mPrivacyListManager.setActiveListName(listName); + } catch (XMPPException e) { + Log.e(TAG, e.getMessage()); + } + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IPrivacyListManager#setDefaultPrivacyList(java.lang.String) + */ + @Override + public void setDefaultPrivacyList(String listName) throws RemoteException { + try { + mPrivacyListManager.setDefaultListName(listName); + } catch (XMPPException e) { + Log.e(TAG, e.getMessage()); + } + } + + /** + * From a List of PrivacyListItem get a List of PrivacyItem. + * @param items The List of PrivacyListItem. + * @return A list of PrivacyItem. + */ + private List tranformPrivacyListItemsToPrivacyItems(List items) { + List rItems = new ArrayList(); + PrivacyItem.Type[] itemTypes = PrivacyItem.Type.values(); + + for (int i = 0; i < items.size(); i++) { + rItems.add(new PrivacyItem(itemTypes[items.get(i).getType()].name(), false, i)); + } + + return rItems; + } + + /** + * From a List of PrivacyItem get a List of PrivacyListItem. + * @param items The List of PrivacyItem. + * @return A list of PrivacyListItem. + */ + private List tranformPrivacyItemsToPrivacyListItems(List items) { + List rItems = new ArrayList(); + + for (int i = 0; i < items.size(); i++) { + rItems.add(new PrivacyListItem(items.get(i).getType().ordinal(), items.get(i).getValue())); + } + return rItems; + } + + /** + * An adapter for the Smack's PrivacyListListener. + * @author Jean-Manuel Da Silva + */ + private class PrivacyListListenerAdapter implements PrivacyListListener { + /** + * Constructor. + */ + public PrivacyListListenerAdapter() { } + + @Override + public void setPrivacyList(final String listName, final List listItem) { + int i = mPrivacyListListeners.beginBroadcast(); + while (i > 0) { + i--; + try { + mPrivacyListListeners.getBroadcastItem(i).setPrivacyList(listName, + tranformPrivacyItemsToPrivacyListItems(listItem)); + } catch (RemoteException e) { + Log.w(TAG, e.getMessage()); + } + } + mPrivacyListListeners.finishBroadcast(); + } + + @Override + public void updatedPrivacyList(final String listName) { + Log.d(TAG, "BEGIN updatedPrivacyList."); + int i = mPrivacyListListeners.beginBroadcast(); + while (i > 0) { + i--; + try { + mPrivacyListListeners.getBroadcastItem(i).updatedPrivacyList(listName); + } catch (RemoteException e) { + Log.w(TAG, e.getMessage()); + } + } + mPrivacyListListeners.finishBroadcast(); + Log.d(TAG, "END updatedPrivacyList."); + } + } + + @Override + public void addPrivacyListListener(IPrivacyListListener listener) throws RemoteException { + if (listener != null) + mPrivacyListListeners.register(listener); + } + + @Override + public void removePrivacyListListener(IPrivacyListListener listener) throws RemoteException { + if (listener != null) + mPrivacyListListeners.unregister(listener); + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IPrivacyListManager#getPrivacyLists() + */ + @Override + public List getPrivacyLists() throws RemoteException { + Log.d(TAG, "BEGIN getPrivacyLists."); + List res = new ArrayList(); + try { + PrivacyList[] registeredPrivacyLists = mPrivacyListManager.getPrivacyLists(); + Log.d(TAG, "> registeredPrivacyLists size: " + registeredPrivacyLists.length); + if (registeredPrivacyLists.length > 0) { + for (int i = 0; i < registeredPrivacyLists.length; i++) { + res.add(registeredPrivacyLists[i].toString()); + Log.d(TAG, "> " + res.get(i) + " added."); + } + } + } catch (XMPPException e) { + Log.e(TAG, e.getMessage()); + } + Log.d(TAG, "END getPrivacyLists."); + return res; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/RosterAdapter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/RosterAdapter.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,416 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import android.content.Context; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Log; + +import com.beem.project.beem.R; +import com.beem.project.beem.service.aidl.IBeemRosterListener; +import com.beem.project.beem.smack.avatar.AvatarListener; +import com.beem.project.beem.smack.avatar.AvatarManager; +import com.beem.project.beem.smack.avatar.AvatarMetadataExtension.Info; +import com.beem.project.beem.utils.Status; + +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterEntry; +import org.jivesoftware.smack.RosterGroup; +import org.jivesoftware.smack.RosterListener; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; + + +/** + * This class implement a Roster adapter for BEEM. + */ +public class RosterAdapter extends com.beem.project.beem.service.aidl.IRoster.Stub { + + private static final String TAG = "RosterAdapter"; + private final Roster mAdaptee; + private final RemoteCallbackList mRemoteRosListeners = + new RemoteCallbackList(); + private final Map mDefaultStatusMessages; + private final RosterListenerAdapter mRosterListener = new RosterListenerAdapter(); + private Map mAvatarIdmap = new HashMap(); + private AvatarManager mAvatarManager; + + /** + * Constructor. + * @param roster The roster to adapt. + * @param context The context of the RosterAdapter. + */ + public RosterAdapter(final Roster roster, final Context context) { + mAdaptee = roster; + roster.addRosterListener(mRosterListener); + mDefaultStatusMessages = createDefaultStatusMessagesMap(context); + } + + /** + * Constructor. + * @param roster The roster to adapt. + * @param context The context of the RosterAdapter. + * @param avatarMgr The AvatarManager of the connection + */ + public RosterAdapter(final Roster roster, final Context context, final AvatarManager avatarMgr) { + mAdaptee = roster; + roster.addRosterListener(mRosterListener); + mDefaultStatusMessages = createDefaultStatusMessagesMap(context); + mAvatarManager = avatarMgr; + if (mAvatarManager != null) + mAvatarManager.addAvatarListener(new AvatarEventListener()); + } + + + /** + * {@inheritDoc} + */ + @Override + public void addRosterListener(IBeemRosterListener listen) throws RemoteException { + if (listen != null) + mRemoteRosListeners.register(listen); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean addContact(String user, String name, String[] groups) throws RemoteException { + RosterEntry contact = mAdaptee.getEntry(user); + try { + mAdaptee.createEntry(user, name, groups); + contact = mAdaptee.getEntry(user); + } catch (XMPPException e) { + Log.e(TAG, "Error while adding new contact", e); + return false; + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void deleteContact(Contact contact) throws RemoteException { + try { + RosterEntry entry = mAdaptee.getEntry(contact.getJID()); + mAdaptee.removeEntry(entry); + } catch (XMPPException e) { + e.printStackTrace(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void createGroup(String groupname) throws RemoteException { + if (mAdaptee.getGroup(groupname) == null) + mAdaptee.createGroup(groupname); + } + + /** + * {@inheritDoc} + */ + @Override + public Contact getContact(String jid) throws RemoteException { + if (mAdaptee.contains(jid)) + return getContactFromRosterEntry(mAdaptee.getEntry(jid)); + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public List getContactList() throws RemoteException { + boolean add = true; + Collection list = mAdaptee.getEntries(); + List coList = new ArrayList(list.size()); + for (RosterEntry entry : list) { + coList.add(getContactFromRosterEntry(entry)); + } + return coList; + } + + /** + * {@inheritDoc} + */ + @Override + public List getGroupsNames() throws RemoteException { + Collection groups = mAdaptee.getGroups(); + List result = new ArrayList(groups.size()); + for (RosterGroup rosterGroup : groups) { + result.add(rosterGroup.getName()); + } + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public void removeRosterListener(IBeemRosterListener listen) throws RemoteException { + if (listen != null) + mRemoteRosListeners.unregister(listen); + } + + /** + * {@inheritDoc} + */ + @Override + public void setContactName(String jid, String name) throws RemoteException { + mAdaptee.getEntry(jid).setName(name); + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IRoster#getPresence(java.lang.String) + */ + @Override + public PresenceAdapter getPresence(String jid) throws RemoteException { + return new PresenceAdapter(mAdaptee.getPresence(jid)); + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IRoster#addContactToGroup(java.lang.String, java.lang.String) + */ + @Override + public void addContactToGroup(String groupName, String jid) throws RemoteException { + createGroup(groupName); + RosterGroup group = mAdaptee.getGroup(groupName); + try { + group.addEntry(mAdaptee.getEntry(jid)); + } catch (XMPPException e) { + e.printStackTrace(); + } + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IRoster#removeContactFromGroup(java.lang.String, java.lang.String) + */ + @Override + public void removeContactFromGroup(String groupName, String jid) throws RemoteException { + RosterGroup group = mAdaptee.getGroup(groupName); + try { + group.removeEntry(mAdaptee.getEntry(jid)); + } catch (XMPPException e) { + e.printStackTrace(); + } + } + + /** + * Get a contact from a RosterEntry. + * @param entry a roster entry containing information for the contact. + * @return a contact for this entry. + */ + private Contact getContactFromRosterEntry(RosterEntry entry) { + String user = entry.getUser(); + Contact c = new Contact(user); + Presence p = mAdaptee.getPresence(user); + + if (p.getStatus() == null || "".equals(p.getStatus())) + p.setStatus(mDefaultStatusMessages.get(Status.getStatusFromPresence(p))); + c.setStatus(p); + try { + c.setGroups(entry.getGroups()); + } catch (NullPointerException e) { + Log.d(TAG, "Group list not ready", e); + } + Iterator iPres = mAdaptee.getPresences(user); + while (iPres.hasNext()) { + p = iPres.next(); + if (!p.getType().equals(Presence.Type.unavailable)) + c.addRes(StringUtils.parseResource(p.getFrom())); + } + c.setName(entry.getName()); + c.setAvatarId(mAvatarIdmap.get(user)); + return c; + } + + /** + * Create a map which contains default status messages. + * @param context The context of the roster adapter. + * @return A Map which assigns a status to a message. + */ + private Map createDefaultStatusMessagesMap(Context context) { + Map defaultStatusMessages = new HashMap(); + defaultStatusMessages.put(Status.CONTACT_STATUS_AVAILABLE, context + .getString(R.string.contact_status_msg_available)); + defaultStatusMessages.put(Status.CONTACT_STATUS_AVAILABLE_FOR_CHAT, context + .getString(R.string.contact_status_msg_available_chat)); + defaultStatusMessages.put(Status.CONTACT_STATUS_AWAY, context.getString(R.string.contact_status_msg_away)); + defaultStatusMessages.put(Status.CONTACT_STATUS_BUSY, context.getString(R.string.contact_status_msg_dnd)); + defaultStatusMessages.put(Status.CONTACT_STATUS_DISCONNECT, context + .getString(R.string.contact_status_msg_offline)); + defaultStatusMessages.put(Status.CONTACT_STATUS_UNAVAILABLE, context.getString(R.string.contact_status_msg_xa)); + + return defaultStatusMessages; + } + + /** + * Listener for the roster events. It will call the remote listeners registered. + * @author darisk + */ + private class RosterListenerAdapter implements RosterListener { + + /** + * Constructor. + */ + public RosterListenerAdapter() { + } + + /** + * {@inheritDoc} + */ + @Override + public void entriesAdded(Collection addresses) { + final int n = mRemoteRosListeners.beginBroadcast(); + + List tab = new ArrayList(); + tab.addAll(addresses); + for (int i = 0; i < n; i++) { + IBeemRosterListener listener = mRemoteRosListeners.getBroadcastItem(i); + try { + listener.onEntriesAdded(tab); + } catch (RemoteException e) { + Log.w(TAG, "Error while adding roster entries", e); + } + } + mRemoteRosListeners.finishBroadcast(); + } + + /** + * {@inheritDoc} + */ + @Override + public void entriesDeleted(Collection addresses) { + final int n = mRemoteRosListeners.beginBroadcast(); + + List tab = new ArrayList(); + tab.addAll(addresses); + for (int i = 0; i < n; i++) { + IBeemRosterListener listener = mRemoteRosListeners.getBroadcastItem(i); + try { + listener.onEntriesDeleted(tab); + } catch (RemoteException e) { + Log.w(TAG, "Error while deleting roster entries", e); + } + } + mRemoteRosListeners.finishBroadcast(); + } + + /** + * {@inheritDoc} + */ + @Override + public void entriesUpdated(Collection addresses) { + final int n = mRemoteRosListeners.beginBroadcast(); + + List tab = new ArrayList(); + tab.addAll(addresses); + for (int i = 0; i < n; i++) { + IBeemRosterListener listener = mRemoteRosListeners.getBroadcastItem(i); + try { + listener.onEntriesUpdated(tab); + } catch (RemoteException e) { + Log.w(TAG, "Error while updating roster entries", e); + } + } + mRemoteRosListeners.finishBroadcast(); + } + + /** + * {@inheritDoc} + */ + @Override + public void presenceChanged(Presence presence) { + final int n = mRemoteRosListeners.beginBroadcast(); + Log.v(TAG, ">>> Presence changed for " + presence.getFrom()); + + for (int i = 0; i < n; i++) { + IBeemRosterListener listener = mRemoteRosListeners.getBroadcastItem(i); + try { + if (presence.getStatus() == null || "".equals(presence.getStatus())) { + presence.setStatus(mDefaultStatusMessages.get(Status.getStatusFromPresence(presence))); + } + listener.onPresenceChanged(new PresenceAdapter(presence)); + } catch (RemoteException e) { + Log.w(TAG, "Error while updating roster presence entries", e); + } + } + mRemoteRosListeners.finishBroadcast(); + } + } + + /** + * Listener on avatar metadata event. + * + */ + private class AvatarEventListener implements AvatarListener { + + /** + * Constructor. + */ + public AvatarEventListener() { } + + @Override + public void onAvatarChange(String from, String avatarId, List avatarInfos) { + String bare = StringUtils.parseBareAddress(from); + if (avatarId == null) + mAvatarIdmap.remove(bare); + else if (avatarInfos.size() > 0) { + mAvatarIdmap.put(bare, avatarId); + } + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/UserInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/UserInfo.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,128 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class contains information about the user of the connection. + * These informations are sent by the connection. + * + */ +public class UserInfo implements Parcelable { + + /** Parcelable.Creator needs by Android. */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public UserInfo createFromParcel(Parcel source) { + return new UserInfo(source); + } + + @Override + public UserInfo[] newArray(int size) { + return new UserInfo[size]; + } + }; + + private final String mFullJid; + private String mAvatarId; + + /** + * Construct a UserInfo from a parcel. + * @param in parcel to use for construction + */ + private UserInfo(final Parcel in) { + mFullJid = in.readString(); + mAvatarId = in.readString(); + } + + /** + * Constructor. + * @param jid jid of the user + */ + public UserInfo(final String jid) { + // the jid is case insensitive + mFullJid = jid; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mFullJid); + dest.writeString(mAvatarId); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Get the avatar id of the user. + * + * @return the avatar id + */ + public String getAvatarId() { + return mAvatarId; + } + + /** + * Set the avater id of the user. + * + * @param avatarId the avatar id + */ + public void setAvatarId(String avatarId) { + mAvatarId = avatarId; + } + + /** + * Get the full jid of the user. + * + * @return the jid + */ + public String getJid() { + return mFullJid; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/XmppConnectionAdapter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/XmppConnectionAdapter.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,773 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + + */ +package com.beem.project.beem.service; + +import java.util.Iterator; +import java.util.List; + +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.support.v4.app.NotificationCompat; +import android.util.Log; + +import com.beem.project.beem.BeemApplication; +import com.beem.project.beem.BeemService; +import com.beem.project.beem.R; +import com.beem.project.beem.service.aidl.IBeemConnectionListener; +import com.beem.project.beem.service.aidl.IChatManager; +import com.beem.project.beem.service.aidl.IRoster; +import com.beem.project.beem.service.aidl.IXmppConnection; +import com.beem.project.beem.smack.avatar.AvatarCache; +import com.beem.project.beem.smack.avatar.AvatarListener; +import com.beem.project.beem.smack.avatar.AvatarMetadataExtension; +import com.beem.project.beem.smack.pep.PepSubManager; +import com.beem.project.beem.smack.ping.PingExtension; +import com.beem.project.beem.ui.ChangeStatus; +import com.beem.project.beem.ui.Subscription; +import com.beem.project.beem.utils.BeemBroadcastReceiver; +import com.beem.project.beem.utils.Status; + +import org.jivesoftware.smack.ConnectionConfiguration; +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.PrivacyListManager; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.ChatStateManager; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.entitycaps.EntityCapsManager; +import org.jivesoftware.smackx.packet.DiscoverInfo; + +/** + * This class implements an adapter for XMPPConnection. + * @author darisk + */ +public class XmppConnectionAdapter extends IXmppConnection.Stub { + + /** + * Beem connection closed Intent name. + */ + + private static final int SMACK_PRIORITY_MIN = -128; + private static final int SMACK_PRIORITY_MAX = 128; + private static final String TAG = "XMPPConnectionAdapter"; + private final XMPPConnection mAdaptee; + private IChatManager mChatManager; + private final String mLogin; + private final String mPassword; + private String mResource; + private String mErrorMsg; + private RosterAdapter mRoster; + private int mPreviousPriority; + private int mPreviousMode; + private String mPreviousStatus; + private PrivacyListManagerAdapter mPrivacyListManager; + private ChatStateManager mChatStateManager; + private final BeemService mService; + private BeemApplication mApplication; + private BeemAvatarManager mAvatarManager; + private PepSubManager mPepManager; + private SharedPreferences mPref; + private final RemoteCallbackList mRemoteConnListeners = + new RemoteCallbackList(); + private final SubscribePacketListener mSubscribePacketListener = new SubscribePacketListener(); + private final PingListener mPingListener = new PingListener(); + + private final ConnexionListenerAdapter mConListener = new ConnexionListenerAdapter(); + + private UserInfo mUserInfo; + private final UserInfoManager mUserInfoManager = new UserInfoManager(); + + /** + * Constructor. + * @param config Configuration to use in order to connect + * @param login login to use on connect + * @param password password to use on connect + * @param service the background service associated with the connection. + */ + public XmppConnectionAdapter(final ConnectionConfiguration config, final String login, final String password, + final BeemService service) { + this(new XMPPConnection(config), login, password, service); + } + + /** + * Constructor. + * @param serviceName name of the service to connect to + * @param login login to use on connect + * @param password password to use on connect + * @param service the background service associated with the connection. + */ + public XmppConnectionAdapter(final String serviceName, final String login, final String password, + final BeemService service) { + this(new XMPPConnection(serviceName), login, password, service); + } + + /** + * Constructor. + * @param con The connection to adapt + * @param login The login to use + * @param password The password to use + * @param service the background service associated with the connection. + */ + public XmppConnectionAdapter(final XMPPConnection con, final String login, final String password, + final BeemService service) { + mAdaptee = con; + PrivacyListManager.getInstanceFor(mAdaptee); + mLogin = login; + mPassword = password; + mService = service; + Context ctx = mService.getApplicationContext(); + if (ctx instanceof BeemApplication) { + mApplication = (BeemApplication) ctx; + } + mPref = mService.getServicePreference(); + try { + mPreviousPriority = Integer.parseInt(mPref.getString(BeemApplication.CONNECTION_PRIORITY_KEY, "0")); + } catch (NumberFormatException ex) { + mPreviousPriority = 0; + } + mResource = mPref.getString(BeemApplication.CONNECTION_RESOURCE_KEY, "Beem"); + } + + /** + * {@inheritDoc} + */ + @Override + public void addConnectionListener(IBeemConnectionListener listen) throws RemoteException { + if (listen != null) + mRemoteConnListeners.register(listen); + } + + @Override + public boolean connect() throws RemoteException { + if (mAdaptee.isConnected()) + return true; + else { + try { + mAdaptee.connect(); + mAdaptee.addConnectionListener(mConListener); + return true; + } catch (XMPPException e) { + Log.e(TAG, "Error while connecting", e); + try { + //TODO NIKITA DOES SOME SHIT !!! Fix this monstruosity + String str = mService.getResources().getString( + mService.getResources().getIdentifier(e.getXMPPError().getCondition().replace("-", "_"), + "string", "com.beem.project.beem")); + mErrorMsg = str; + } catch (NullPointerException e2) { + if (!"".equals(e.getMessage())) + mErrorMsg = e.getMessage(); + else + mErrorMsg = e.toString(); + } + } + return false; + } + } + + @Override + public boolean login() throws RemoteException { + if (mAdaptee.isAuthenticated()) + return true; + if (!mAdaptee.isConnected()) + return false; + try { + + this.initFeatures(); // pour declarer les features xmpp qu'on + // supporte + + PacketFilter filter = new PacketFilter() { + + @Override + public boolean accept(Packet packet) { + if (packet instanceof Presence) { + Presence pres = (Presence) packet; + if (pres.getType() == Presence.Type.subscribe) + return true; + } + return false; + } + }; + + mAdaptee.addPacketListener(mSubscribePacketListener, filter); + + filter = new PacketTypeFilter(PingExtension.class); + mAdaptee.addPacketListener(mPingListener, filter); + + mAdaptee.login(mLogin, mPassword, mResource); + mUserInfo = new UserInfo(mAdaptee.getUser()); + discoverServerFeatures(); + + mChatManager = new BeemChatManager(mAdaptee.getChatManager(), mService, mAdaptee.getRoster()); + //nikita: I commented this line because of the logs provided in http://www.beem-project.com/issues/321 + //Also, since the privacylistmanager isn't finished and used, it will be safer to not initialize it + //mPrivacyListManager = new PrivacyListManagerAdapter(PrivacyListManager.getInstanceFor(mAdaptee)); + mService.initJingle(mAdaptee); + + mApplication.setConnected(true); + int mode = mPref.getInt(BeemApplication.STATUS_KEY, 0); + String status = mPref.getString(BeemApplication.STATUS_TEXT_KEY, ""); + changeStatus(mode, status); + return true; + } catch (XMPPException e) { + Log.e(TAG, "Error while connecting", e); + mErrorMsg = mService.getString(R.string.error_login_authentication); + return false; + } + } + + /** + * {@inheritDoc} + */ + @Override + public final void connectAsync() throws RemoteException { + if (mAdaptee.isConnected() || mAdaptee.isAuthenticated()) + return; + Thread t = new Thread(new Runnable() { + + @Override + public void run() { + try { + connectSync(); + } catch (RemoteException e) { + Log.e(TAG, "Error while connecting asynchronously", e); + } + } + }); + t.start(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean connectSync() throws RemoteException { + if (connect()) + return login(); + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void changeStatusAndPriority(int status, String msg, int priority) { + Presence pres = new Presence(Presence.Type.available); + String m; + if (msg != null) + m = msg; + else + m = mPreviousStatus; + pres.setStatus(m); + mPreviousStatus = m; + Presence.Mode mode = Status.getPresenceModeFromStatus(status); + if (mode != null) { + pres.setMode(mode); + mPreviousMode = status; + } else { + pres.setMode(Status.getPresenceModeFromStatus(mPreviousMode)); + } + int p = priority; + if (priority < SMACK_PRIORITY_MIN) + p = SMACK_PRIORITY_MIN; + if (priority > SMACK_PRIORITY_MAX) + p = SMACK_PRIORITY_MAX; + mPreviousPriority = p; + pres.setPriority(p); + mAdaptee.sendPacket(pres); + updateNotification(Status.getStatusFromPresence(pres), m); + } + + /** + * {@inheritDoc} + */ + @Override + public void changeStatus(int status, String msg) { + changeStatusAndPriority(status, msg, mPreviousPriority); + } + + /** + * Get the AvatarManager of this connection. + * @return the AvatarManager or null if there is not + */ + public BeemAvatarManager getAvatarManager() { + return mAvatarManager; + } + + /** + * get the previous status. + * @return previous status. + */ + public String getPreviousStatus() { + return mPreviousStatus; + } + + /** + * get the previous mode. + * @return previous mode. + */ + public int getPreviousMode() { + return mPreviousMode; + } + + /** + * Update the notification for the Beem status. + * @param status the status to display. + * @param text the text to display. + */ + private void updateNotification(int status, String text) { + Notification mStatusNotification; + mStatusNotification = new Notification(Status.getIconBarFromStatus(status), text, System.currentTimeMillis()); + mStatusNotification.defaults = Notification.DEFAULT_LIGHTS; + mStatusNotification.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; + + mStatusNotification.setLatestEventInfo(mService, "Beem Status", text, + PendingIntent.getActivity(mService, 0, new Intent(mService, ChangeStatus.class), 0)); + // bypass the preferences for notification + mService.getNotificationManager().notify(BeemService.NOTIFICATION_STATUS_ID, mStatusNotification); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean disconnect() { + if (mAdaptee != null && mAdaptee.isConnected()) + mAdaptee.disconnect(); + return true; + } + + /** + * Get the Smack XmppConnection. + * @return Smack XmppConnection + */ + public XMPPConnection getAdaptee() { + return mAdaptee; + } + + /** + * {@inheritDoc} + */ + @Override + public IChatManager getChatManager() throws RemoteException { + return mChatManager; + } + + /** + * {@inheritDoc} + */ + @Override + public IRoster getRoster() throws RemoteException { + if (mRoster != null) + return mRoster; + Roster adap = mAdaptee.getRoster(); + if (adap == null) + return null; + mRoster = new RosterAdapter(adap, mService, mAvatarManager); + return mRoster; + } + + /** + * Get the user informations. + * @return the user infos or null if not logged + */ + public UserInfo getUserInfo() { + return mUserInfo; + } + + /** + * Returns true if currently authenticated by successfully calling the login method. + * @return true when successfully authenticated + */ + public boolean isAuthentificated() { + return mAdaptee.isAuthenticated(); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeConnectionListener(IBeemConnectionListener listen) throws RemoteException { + if (listen != null) + mRemoteConnListeners.unregister(listen); + } + + /** + * PrivacyListManagerAdapter mutator. + * @param privacyListManager the privacy list manager + */ + public void setPrivacyListManager(PrivacyListManagerAdapter privacyListManager) { + this.mPrivacyListManager = privacyListManager; + } + + /** + * PrivacyListManagerAdapter accessor. + * @return the mPrivacyList + */ + public PrivacyListManagerAdapter getPrivacyListManager() { + return mPrivacyListManager; + } + + /** + * {@inheritDoc} + */ + @Override + public String getErrorMessage() { + return mErrorMsg; + } + + /** + * Initialize the features provided by beem. + */ + private void initFeatures() { + ServiceDiscoveryManager.setIdentityName("Beem"); + ServiceDiscoveryManager.setIdentityType("phone"); + ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(mAdaptee); + if (sdm == null) + sdm = new ServiceDiscoveryManager(mAdaptee); + + sdm.addFeature("http://jabber.org/protocol/disco#info"); + //nikita: must be uncommented when the feature will be enabled + //sdm.addFeature("jabber:iq:privacy"); + sdm.addFeature("http://jabber.org/protocol/caps"); + sdm.addFeature("urn:xmpp:avatar:metadata"); + sdm.addFeature("urn:xmpp:avatar:metadata+notify"); + sdm.addFeature("urn:xmpp:avatar:data"); + sdm.addFeature("http://jabber.org/protocol/nick"); + sdm.addFeature("http://jabber.org/protocol/nick+notify"); + sdm.addFeature(PingExtension.NAMESPACE); + + mChatStateManager = ChatStateManager.getInstance(mAdaptee); + + EntityCapsManager em = sdm.getEntityCapsManager(); + em.setNode("http://www.beem-project.com"); + } + + /** + * Discover the features provided by the server. + */ + private void discoverServerFeatures() { + try { + // jid et server + ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(mAdaptee); + DiscoverInfo info = sdm.discoverInfo(mAdaptee.getServiceName()); + Iterator it = info.getIdentities(); + while (it.hasNext()) { + DiscoverInfo.Identity identity = it.next(); + if ("pubsub".equals(identity.getCategory()) && "pep".equals(identity.getType())) { + initPEP(); + } + } + } catch (XMPPException e) { + Log.w(TAG, "Unable to discover server features", e); + } + } + + /** + * Initialize PEP. + */ + private void initPEP() { + // Enable pep sending + Log.d(TAG, "Pep enabled"); + // API 8 + // mService.getExternalCacheDir() + mPepManager = new PepSubManager(mAdaptee, StringUtils.parseBareAddress(mAdaptee.getUser())); + AvatarCache avatarCache = new BeemAvatarCache(mService); + mAvatarManager = new BeemAvatarManager(mService, mAdaptee, mPepManager, avatarCache, true); + mAvatarManager.addAvatarListener(mUserInfoManager); + mApplication.setPepEnabled(true); + } + + /** + * Reset the application state. + */ + private void resetApplication() { + mApplication.setConnected(false); + mApplication.setPepEnabled(false); + } + + /** + * Listener for XMPP connection events. It will calls the remote listeners for connection events. + */ + private class ConnexionListenerAdapter implements ConnectionListener { + + /** + * Defaut constructor. + */ + public ConnexionListenerAdapter() { + } + + /** + * {@inheritDoc} + */ + @Override + public void connectionClosed() { + Log.d(TAG, "closing connection"); + mRoster = null; + Intent intent = new Intent(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED); + intent.putExtra("message", mService.getString(R.string.BeemBroadcastReceiverDisconnect)); + intent.putExtra("normally", true); + mService.sendBroadcast(intent); + mService.stopSelf(); + resetApplication(); + } + + /** + * {@inheritDoc} + */ + @Override + public void connectionClosedOnError(Exception exception) { + Log.d(TAG, "connectionClosedOnError"); + mRoster = null; + Intent intent = new Intent(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED); + intent.putExtra("message", exception.getMessage()); + mService.sendBroadcast(intent); + mService.stopSelf(); + resetApplication(); + } + + /** + * Connection failed callback. + * @param errorMsg smack failure message + */ + public void connectionFailed(String errorMsg) { + Log.d(TAG, "Connection Failed"); + final int n = mRemoteConnListeners.beginBroadcast(); + + for (int i = 0; i < n; i++) { + IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i); + try { + if (listener != null) + listener.connectionFailed(errorMsg); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the + // dead listeners. + Log.w(TAG, "Error while triggering remote connection listeners", e); + } + } + mRemoteConnListeners.finishBroadcast(); + mService.stopSelf(); + resetApplication(); + } + + /** + * {@inheritDoc} + */ + @Override + public void reconnectingIn(int arg0) { + Log.d(TAG, "reconnectingIn"); + final int n = mRemoteConnListeners.beginBroadcast(); + + for (int i = 0; i < n; i++) { + IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i); + try { + if (listener != null) + listener.reconnectingIn(arg0); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the + // dead listeners. + Log.w(TAG, "Error while triggering remote connection listeners", e); + } + } + mRemoteConnListeners.finishBroadcast(); + } + + /** + * {@inheritDoc} + */ + @Override + public void reconnectionFailed(Exception arg0) { + Log.d(TAG, "reconnectionFailed"); + final int r = mRemoteConnListeners.beginBroadcast(); + + for (int i = 0; i < r; i++) { + IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i); + try { + if (listener != null) + listener.reconnectionFailed(); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the + // dead listeners. + Log.w(TAG, "Error while triggering remote connection listeners", e); + } + } + mRemoteConnListeners.finishBroadcast(); + } + + /** + * {@inheritDoc} + */ + @Override + public void reconnectionSuccessful() { + Log.d(TAG, "reconnectionSuccessful"); + mApplication.setConnected(true); + PacketFilter filter = new PacketFilter() { + + @Override + public boolean accept(Packet packet) { + if (packet instanceof Presence) { + Presence pres = (Presence) packet; + if (pres.getType() == Presence.Type.subscribe) + return true; + } + return false; + } + }; + + mAdaptee.addPacketListener(mSubscribePacketListener, filter); + + final int n = mRemoteConnListeners.beginBroadcast(); + + for (int i = 0; i < n; i++) { + IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i); + try { + if (listener != null) + listener.reconnectionSuccessful(); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing the + // dead listeners. + Log.w(TAG, "Error while triggering remote connection listeners", e); + } + } + mRemoteConnListeners.finishBroadcast(); + } + } + + /** + * This PacketListener will set a notification when you got a subscribtion request. + * @author Da Risk + */ + private class SubscribePacketListener implements PacketListener { + + /** + * Constructor. + */ + public SubscribePacketListener() { + } + + @Override + public void processPacket(Packet packet) { + String from = packet.getFrom(); + + NotificationCompat.Builder notif = new NotificationCompat.Builder(mService); + String title = mService.getString(R.string.AcceptContactRequest, from); + String text = mService.getString(R.string.AcceptContactRequestFrom, from); + notif.setTicker(title).setContentTitle(title); + notif.setContentText(text); + notif.setSmallIcon(R.drawable.beem_status_icon_gray); + notif.setAutoCancel(true).setWhen(System.currentTimeMillis()); + + Intent intent = new Intent(mService, Subscription.class); + intent.setData(Contact.makeXmppUri(from)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + PendingIntent notifIntent = PendingIntent.getActivity(mService, 0, intent, PendingIntent.FLAG_ONE_SHOT); + notif.setContentIntent(notifIntent); + + int id = packet.hashCode(); + mService.sendNotification(id, notif.getNotification()); + } + } + + /** + * The UserInfoManager listen to XMPP events and update the user information accoldingly. + */ + private class UserInfoManager implements AvatarListener { + + /** + * Constructor. + */ + public UserInfoManager() { + } + + @Override + public void onAvatarChange(String from, String avatarId, List avatarInfos) { + String jid = StringUtils.parseBareAddress(mUserInfo.getJid()); + String mfrom = StringUtils.parseBareAddress(from); + if (jid.equalsIgnoreCase(mfrom)) { + mUserInfo.setAvatarId(avatarId); + } + } + } + + /** + * Listener for Ping request. It will respond with a Pong. + */ + private class PingListener implements PacketListener { + + /** + * Constructor. + */ + public PingListener() { + + } + + @Override + public void processPacket(Packet packet) { + if (!(packet instanceof PingExtension)) + return; + PingExtension p = (PingExtension) packet; + if (p.getType() == IQ.Type.GET) { + PingExtension pong = new PingExtension(); + pong.setType(IQ.Type.RESULT); + pong.setTo(p.getFrom()); + pong.setPacketID(p.getPacketID()); + mAdaptee.sendPacket(pong); + } + } + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/XmppFacade.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/XmppFacade.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,196 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.service; + +import android.net.Uri; +import android.os.RemoteException; + +import com.beem.project.beem.BeemService; +import com.beem.project.beem.service.aidl.IChatManager; +import com.beem.project.beem.service.aidl.IPrivacyListManager; +import com.beem.project.beem.service.aidl.IRoster; +import com.beem.project.beem.service.aidl.IXmppConnection; +import com.beem.project.beem.service.aidl.IXmppFacade; +import com.beem.project.beem.utils.PresenceType; + +import org.jivesoftware.smack.packet.Presence; + +/** + * This class is a facade for the Beem Service. + * @author darisk + */ +public class XmppFacade extends IXmppFacade.Stub { + + private XmppConnectionAdapter mConnexion; + private final BeemService service; + + /** + * Create an XmppFacade. + * + * @param service the service providing the facade + */ + public XmppFacade(final BeemService service) { + this.service = service; + } + + /** + * {@inheritDoc} + */ + @Override + public void changeStatus(int status, String msg) { + initConnection(); + mConnexion.changeStatus(status, msg); + } + + /** + * {@inheritDoc} + */ + @Override + public void connectAsync() throws RemoteException { + initConnection(); + mConnexion.connectAsync(); + } + + /** + * {@inheritDoc} + */ + @Override + public void connectSync() throws RemoteException { + initConnection(); + mConnexion.connectSync(); + } + + /** + * {@inheritDoc} + */ + @Override + public IXmppConnection createConnection() throws RemoteException { + initConnection(); + return mConnexion; + } + + /** + * {@inheritDoc} + */ + @Override + public void disconnect() throws RemoteException { + initConnection(); + mConnexion.disconnect(); + } + + /** + * {@inheritDoc} + */ + @Override + public IChatManager getChatManager() throws RemoteException { + initConnection(); + return mConnexion.getChatManager(); + } + + /** + * {@inheritDoc} + */ + @Override + public IRoster getRoster() throws RemoteException { + initConnection(); + return mConnexion.getRoster(); + } + + /** + * {@inheritDoc} + */ + @Override + public IPrivacyListManager getPrivacyListManager() { + initConnection(); + return mConnexion.getPrivacyListManager(); + } + + @Override + public void sendPresencePacket(PresenceAdapter presence) throws RemoteException { + initConnection(); + Presence presence2 = new Presence(PresenceType.getPresenceTypeFrom(presence.getType())); + presence2.setTo(presence.getTo()); + mConnexion.getAdaptee().sendPacket(presence2); + } + + /* (non-Javadoc) + * @see com.beem.project.beem.service.aidl.IXmppFacade#call(java.lang.String) + */ + @Override + public void call(String jid) throws RemoteException { + } + + @Override + public boolean publishAvatar(Uri avatarUri) throws RemoteException { + initConnection(); + BeemAvatarManager mgr = mConnexion.getAvatarManager(); + if (mgr == null) + return false; + + return mgr.publishAvatar(avatarUri); + } + + @Override + public void disableAvatarPublishing() throws RemoteException { + initConnection(); + BeemAvatarManager mgr = mConnexion.getAvatarManager(); + if (mgr != null) + mgr.disableAvatarPublishing(); + } + + @Override + public UserInfo getUserInfo() throws RemoteException { + initConnection(); + return mConnexion.getUserInfo(); + } + + /** + * Initialize the connection. + */ + private void initConnection() { + if (mConnexion == null) { + mConnexion = service.createConnection(); + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/auth/AccountAuthenticator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/auth/AccountAuthenticator.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,145 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2012 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ +package com.beem.project.beem.service.auth; + +import java.io.IOException; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.content.Context; +import android.util.Log; + +import org.apache.harmony.javax.security.auth.callback.Callback; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.auth.callback.NameCallback; +import org.apache.harmony.javax.security.auth.callback.PasswordCallback; +import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; +import org.apache.harmony.javax.security.sasl.RealmCallback; +import org.jivesoftware.smack.util.StringUtils; + +/** + * The AccountAuthenticator use an Android Account to authenticate. + */ +public class AccountAuthenticator implements CallbackHandler { + private static final String GOOGLE_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/googletalk"; + private static final String TAG = AccountAuthenticator.class.getSimpleName(); + + private AccountManager accountMgr; + private Account account; + + /** + * Create an AccountAuthenticator. + * + * @param context the Android context + * @param account the account to use + * + */ + public AccountAuthenticator(final Context context, final Account account) { + accountMgr = AccountManager.get(context); + this.account = account; + } + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + String tmpJid = account.name; + String service = StringUtils.parseServer(tmpJid); + + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof NameCallback) { + String authenticationId = StringUtils.parseName(tmpJid); + if (useFullJid(account)) { + authenticationId = tmpJid; + } + NameCallback ncb = (NameCallback) callbacks[i]; + ncb.setName(authenticationId); + } else if (callbacks[i] instanceof PasswordCallback) { + PasswordCallback pcb = (PasswordCallback) callbacks[i]; + // skip if password is asked for PKCS11 (SSL keystore) + String prompt = pcb.getPrompt(); + if (prompt != null && prompt.startsWith("PKCS11 Password:")) + continue; + String password; + if (useToken(account)) + password = getToken(); + else + password = accountMgr.getPassword(account); + if (password == null) + password = ""; + pcb.setPassword(password.toCharArray()); + } else if (callbacks[i] instanceof RealmCallback) { + RealmCallback rcb = (RealmCallback) callbacks[i]; + rcb.setText(service); + } else { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + + /** + * Test if the accout use the full jid to authenticate. + * + * @param accountt the account to test + * + * @return true if the account use full jid false otherwise + */ + private boolean useFullJid(Account accountt) { + String type = accountt.type; + return "com.google".equals(type); + } + + /** + * Test if the account use authentication token. + * + * @param accountt the account to test + * + * @return true if the account use token false otherwise + */ + private boolean useToken(Account accountt) { + String type = accountt.type; + return "com.google".equals(type); + } + + /** + * Get a authentication token from the Account. + * + * @return the token or en empty string if an error occurs + */ + private String getToken() { + try { + return accountMgr.blockingGetAuthToken(account, GOOGLE_TOKEN_TYPE, true); + } catch (OperationCanceledException e) { + Log.v(TAG, "Token request canceled", e); + } catch (AuthenticatorException e) { + Log.d(TAG, "Unable to get token", e); + } catch (IOException e) { + Log.d(TAG, "Unable to get token", e); + } + return ""; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/auth/PreferenceAuthenticator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/auth/PreferenceAuthenticator.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,92 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2012 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ +package com.beem.project.beem.service.auth; + +import java.io.IOException; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import com.beem.project.beem.BeemApplication; + +import org.apache.harmony.javax.security.auth.callback.Callback; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.auth.callback.NameCallback; +import org.apache.harmony.javax.security.auth.callback.PasswordCallback; +import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; +import org.apache.harmony.javax.security.sasl.RealmCallback; +import org.jivesoftware.smack.util.StringUtils; + +/** + * An Authenticator which look for credentials stored in preferences. + */ +public class PreferenceAuthenticator implements CallbackHandler { + + private final SharedPreferences settings; + + /** + * Create a PreferenceAuthenticator. + * + * @param context the Android context. + */ + public PreferenceAuthenticator(final Context context) { + settings = PreferenceManager.getDefaultSharedPreferences(context); + } + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + String tmpJid = settings.getString(BeemApplication.ACCOUNT_USERNAME_KEY, "").trim(); + String service = StringUtils.parseServer(tmpJid); + + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof NameCallback) { + String authenticationId = StringUtils.parseName(tmpJid); + if (settings.getBoolean(BeemApplication.FULL_JID_LOGIN_KEY, false) + || "gmail.com".equals(service) || "googlemail.com".equals(service)) { + authenticationId = tmpJid; + } + NameCallback ncb = (NameCallback) callbacks[i]; + ncb.setName(authenticationId); + } else if (callbacks[i] instanceof PasswordCallback) { + PasswordCallback pcb = (PasswordCallback) callbacks[i]; + // skip if password is asked for PKCS11 (SSL keystore) + String prompt = pcb.getPrompt(); + if (prompt != null && prompt.startsWith("PKCS11 Password:")) + continue; + String password = settings.getString(BeemApplication.ACCOUNT_PASSWORD_KEY, ""); + pcb.setPassword(password.toCharArray()); + } else if (callbacks[i] instanceof RealmCallback) { + RealmCallback rcb = (RealmCallback) callbacks[i]; + rcb.setText(service); + } else { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/auth/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/auth/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,31 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2012 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ + +/** + * This package contains different Authenticator used to login. + */ +package com.beem.project.beem.service.auth; diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/service/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/service/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,47 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. +*/ +/** + * This package contains all the class use by the service. + */ +package com.beem.project.beem.service; + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/AvatarCache.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/AvatarCache.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,90 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.avatar; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Interface for an AvatarCache. + * This can be improved to a generic cache. + * + */ +public interface AvatarCache { + + /** + * Put some datas in cache. + * + * @param id the key id of the data + * @param data the data to cache + * @throws IOException if an IO error occurs while caching the data + */ + void put(String id, byte[] data) throws IOException; + + /** + * Put some datas in cache. + * + * @param id the key id of the data + * @param data an InputStream to the data to cache + * @throws IOException if an IO error occurs while caching the data + */ + void put(String id, InputStream data) throws IOException; + + /** + * Get some data from the cache. + * + * @param id the id of the data to get + * @return the cached data + * @throws IOException if an IO error occurs while geting the data + */ + byte[] get(String id) throws IOException; + + /** + * Test if a data is in cache. + * + * @param id the id of the data + * @return true if data is in cache false otherwise + */ + boolean contains(String id); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/AvatarExtension.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/AvatarExtension.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,111 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.avatar; + +import org.jivesoftware.smack.util.Base64; +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * PacketExtension to represent the Avatar data. + * XML namespace urn:xmpp:avatar:data + * + */ +public class AvatarExtension implements PacketExtension { + + private String mData; + + /** + * Create an AvatarExtension. + * @param base64 the data of the avatar as a base64 string + */ + public AvatarExtension(final String base64) { + mData = base64; + } + + /** + * Create an AvatarExtension. + * @param data the data of the avatar + */ + public AvatarExtension(final byte[] data) { + mData = Base64.encodeBytes(data); + } + + /** + * Get the avatar data as a Base64 string. + * + * @return a base64 string. + */ + public String getBase64() { + return mData; + } + + /** + * Get the avatar data. + * + * @return the decoded data + */ + public byte[] getData() { + return Base64.decode(mData); + } + + @Override + public String getElementName() { + return "data"; + } + + @Override + public String getNamespace() { + return "urn:xmpp:avatar:data"; + } + + @Override + public String toXML() { + StringBuilder builder = new StringBuilder(""); + builder.append(mData); + builder.append(""); + return builder.toString(); + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/AvatarListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/AvatarListener.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,64 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.avatar; + +import java.util.List; +import com.beem.project.beem.smack.avatar.AvatarMetadataExtension.Info; + +/** + * A listener for avatar changes event. + * + */ +public interface AvatarListener { + + /** + * Event which is fired when a contact change avatar. + * + * @param from the contact who change his avatar + * @param avatarId the new avatar id, may be null if the contact set no avatar + * @param avatarInfos the metadata infos of the avatar, may be empty if the contact set no avatar + */ + void onAvatarChange(String from, String avatarId, List avatarInfos); + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/AvatarManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/AvatarManager.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,292 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.avatar; + +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.LinkedList; +import java.util.List; + +import com.beem.project.beem.smack.avatar.AvatarMetadataExtension.Info; +import com.beem.project.beem.smack.pep.PEPListener; +import com.beem.project.beem.smack.pep.PepSubManager; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.pubsub.Item; +import org.jivesoftware.smackx.pubsub.LeafNode; +import org.jivesoftware.smackx.pubsub.PayloadItem; + +/** + * This class deals with the avatar data. + * It can be configured to auto retrieve the avatar and put it in cache. + * + */ +public class AvatarManager { + + /** The pubsub node for avatar data. */ + public static final String AVATARDATA_NODE = "urn:xmpp:avatar:data"; + /** The pubsub node for avatar metadata. */ + public static final String AVATARMETADATA_NODE = "urn:xmpp:avatar:metadata"; + + private PepSubManager mPep; + private Connection mCon; + private boolean mAutoDownload; + private AvatarCache mCache; + private final List mListeners = new LinkedList(); + + /** + * Create an AvatarManager. + * + * @param con the connection + * @param pepMgr the PepSubManager of the Connection + * @param cache the cache which will store the avatars + * @param autoDownload true to enable auto download of avatars + */ + public AvatarManager(final Connection con, final PepSubManager pepMgr, + final AvatarCache cache, final boolean autoDownload) { + mCon = con; + mPep = pepMgr; + mAutoDownload = autoDownload; + mCache = cache; + mPep.addPEPListener(new Listener()); + } + + /** + * Create an AvatarManager. + * + * @param con the connection + * @param pepMgr the PepSubManager of the Connection + * @param autoDownload true to enable auto download of avatars + */ + protected AvatarManager(final Connection con, final PepSubManager pepMgr, final boolean autoDownload) { + mCon = con; + mPep = pepMgr; + mAutoDownload = autoDownload; + mPep.addPEPListener(new Listener()); + mCache = new MemoryAvatarCache(100, 1800000); + } + + /** + * Get an avatar from the cache. + * + * @param avatarId the id of the avatar + * @return the avatar or null if it cannot be retrieved from the cache + */ + public byte[] getAvatar(String avatarId) { + if (avatarId == null) + return null; + try { + return mCache.get(avatarId); + } catch (IOException e) { + return null; + } + } + + /** + * Add an AvatarListener. + * + * @param listener the AvatarListener to add + */ + public void addAvatarListener(AvatarListener listener) { + if (!mListeners.contains(listener)) + mListeners.add(listener); + } + + /** + * Remove an AvatarListener. + * + * @param listener the AvatarListener to remove + */ + public void removeAvatarListener(AvatarListener listener) { + mListeners.remove(listener); + } + + /** + * Download an avatar. + * + * @param from The jid of the user + * @param avatarId the id of the avatar + * @param info the metadata information of the avatar to download + * @return true if the download was successfull + */ + public boolean downloadAvatar(String from, String avatarId, Info info) { + try { + AvatarRetriever retriever = AvatarRetrieverFactory.getRetriever(mCon, from, info); + byte[] avatar = retriever.getAvatar(); + mCache.put(avatarId, avatar); + return true; + } catch (IOException e) { + System.err.println("Error while downloading avatar"); + e.printStackTrace(); + return false; + } + } + + /** + * Disable the diffusion of your avatar. + */ + public void disableAvatarPublishing() { + AvatarMetadataExtension metadata = new AvatarMetadataExtension(); + publishAvatarMetaData(null, metadata); + } + + /** + * Send an avatar image to the pep server. + * + * @param data the image data. + * @return true if the image where successfully sent. false otherwise + */ + public boolean publishAvatarData(byte[] data) { + try { + String id = getAvatarId(data); + publishAvatarData(id, data); + return true; + } catch (NoSuchAlgorithmException e) { + System.err.println("Security error while publishing avatar data : " + e.getMessage()); + return false; + } + } + + /** + * Send the metadata of the avatar you want to publish. + * By sending this metadata, you publish an avatar. + * + * @param id the id of the metadata item + * @param metadata the metadata to publish + */ + public void publishAvatarMetaData(String id, AvatarMetadataExtension metadata) { + PayloadItem item = new PayloadItem(id, metadata); + LeafNode node = mPep.getPEPNode(AVATARMETADATA_NODE); + node.publish(item); + } + + /** + * Select the avatar to download. + * Subclass should override this method to take control over the selection process. + * This implementation select the first element. + * + * @param available list of the avatar metadata information + * @return the metadata of the avatar to download + */ + protected Info selectAvatar(List available) { + return available.get(0); + } + + + /** + * Get the id corresponding to this avatar data. + * + * @param data the avatar data + * @return the id + * @throws NoSuchAlgorithmException if the sha-1 algorithm is unavailable + */ + protected String getAvatarId(byte[] data) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("sha-1"); + byte[] hash = md.digest(data); + return StringUtils.encodeHex(hash); + } + + /** + * Publish an avatar data. + * + * @param id the id of the avatar data + * @param data the data of the avatar + */ + private void publishAvatarData(String id, byte[] data) { + AvatarExtension avatar = new AvatarExtension(data); + PayloadItem item = new PayloadItem(id, avatar); + LeafNode node = mPep.getPEPNode(AVATARDATA_NODE); + node.publish(item); + } + + /** + * Fire the listeners for avatar change. + * + * @param from the jid of the contact + * @param avatarId the new avatar id + * @param avatarInfos the metadata infos of the avatar + */ + private void fireListeners(String from, String avatarId, List avatarInfos) { + for (AvatarListener l : mListeners) + l.onAvatarChange(from, avatarId, avatarInfos); + } + + + /** + * A listener to PEPEevent. + */ + private class Listener implements PEPListener { + + /** + * Create a listener. + */ + public Listener() { + } + + @Override + public void eventReceived(String from, String node, List items) { + if (!"urn:xmpp:avatar:metadata".equals(node)) + return; + Item i = items.get(0); + if (i instanceof PayloadItem) { + PayloadItem pi = (PayloadItem) i; + PacketExtension ex = pi.getPayload(); + if (ex instanceof AvatarMetadataExtension) { + AvatarMetadataExtension ext = (AvatarMetadataExtension) ex; + String id = i.getId(); + List infos = ext.getInfos(); + if (infos.size() > 0 && mAutoDownload) { + Info info = selectAvatar(infos); + if (!mCache.contains(id)) + downloadAvatar(from, id, info); + } + fireListeners(from, id, infos); + } + } + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/AvatarMetadataExtension.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/AvatarMetadataExtension.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,257 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.avatar; + +import java.util.LinkedList; +import java.util.List; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * PacketExtension to represent the Avatar metadata. + * XML namespace urn:xmpp:avatar:metadata + * + */ +public class AvatarMetadataExtension implements PacketExtension { + private List mInfos = new LinkedList(); + + /** + * Create an AvatarMetadataExtension. + */ + public AvatarMetadataExtension() { + } + + /** + * Get the metadata informations. + * + * @return a list of informations + */ + public List getInfos() { + return mInfos; + } + + /** + * Add a metadate information. + * + * @param info the metadata information to add + */ + public void addInfo(Info info) { + mInfos.add(info); + } + + @Override + public String getElementName() { + return "metadata"; + } + + @Override + public String getNamespace() { + return "urn:xmpp:avatar:metadata"; + } + + @Override + public String toXML() { + StringBuilder builder = new StringBuilder(""); + for (Info info : mInfos) { + builder.append(info.toXML()); + } + builder.append(""); + return builder.toString(); + } + + /** + * A metadata information element. + */ + public static class Info { + private int mBytes; + private int mHeight; + private int mWidth; + private String mId; + private String mType; + private String mUrl; + + /** + * Create an Info. + * + * @param id the id of the info + * @param type the MIME type of the avatar + * @param bytes the size of the avatar in bytes + */ + public Info(final String id, final String type, final int bytes) { + mId = id; + mType = type; + mBytes = bytes; + } + + /** + * Set the size of the avatar in bytes. + * + * @param bytes the size + */ + public void setBytes(int bytes) { + this.mBytes = bytes; + } + + /** + * Set the size of the avatar in bytes. + * + * @return the size + */ + public int getBytes() { + return mBytes; + } + + /** + * Set the height. + * + * @param height the height + */ + public void setHeight(int height) { + this.mHeight = height; + } + + /** + * Get the height. + * + * @return the height + */ + public int getHeight() { + return mHeight; + } + + /** + * Set the width. + * + * @param width the width + */ + public void setWidth(int width) { + this.mWidth = width; + } + + /** + * Get the width. + * + * @return the width + */ + public int getWidth() { + return mWidth; + } + + /** + * Set the url. + * + * @param url the url + */ + public void setUrl(String url) { + this.mUrl = url; + } + + /** + * Get the url. + * + * @return the url, null if no url is present + */ + public String getUrl() { + return mUrl; + } + + /** + * Get the id. + * + * @return the id + */ + public String getId() { + return mId; + } + + /** + * Set the id. + * + * @param id the id + */ + public void setId(String id) { + this.mId = id; + } + + /** + * Set the MIME type of the avatar. + * + * @param type the type + */ + public void setType(String type) { + this.mType = type; + } + + /** + * Get the MIME type of the avatar. + * + * @return the type, null if no type is present + */ + public String getType() { + return mType; + } + + /** + * Return this information as an xml element. + * + * @return an xml element representing this information + */ + public String toXML() { + StringBuilder builder = new StringBuilder(" 0) + builder.append(" height=\"" + mHeight + "\""); + if (mWidth > 0) + builder.append(" width=\"" + mWidth + "\""); + if (mUrl != null) + builder.append(" url=\"" + mUrl + "\""); + builder.append(" />"); + return builder.toString(); + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/AvatarMetadataProvider.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/AvatarMetadataProvider.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,110 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.avatar; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * A PacketExtensionProvider to parse the Avatar metadata. + * XML namespace urn:xmpp:avatar:metadata + */ +public class AvatarMetadataProvider implements PacketExtensionProvider { + + /** + * Creates a new AvatarMetadataProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor + */ + public AvatarMetadataProvider() { + } + + @Override + public PacketExtension parseExtension(XmlPullParser parser) + throws Exception { + AvatarMetadataExtension metadata = new AvatarMetadataExtension(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if ("info".equals(parser.getName())) { + String id = parser.getAttributeValue(null, "id"); + String type = parser.getAttributeValue(null, "type"); + String sbytes = parser.getAttributeValue(null, "bytes"); + String sheight = parser.getAttributeValue(null, "height"); + String swidth = parser.getAttributeValue(null, "width"); + int bytes = 0; + AvatarMetadataExtension.Info info = null; + try { + if (sbytes != null) + bytes = Integer.parseInt(sbytes); + } catch (NumberFormatException e) { } + if (bytes != 0 && id != null && type != null) + info = new AvatarMetadataExtension.Info(id, type, bytes); + else // invalid info + continue; + + String url = parser.getAttributeValue(null, "url"); + info.setUrl(url); + try { + int height = 0; + int width = 0; + if (sheight != null) + height = Integer.parseInt(parser.getAttributeValue(null, "height")); + if (swidth != null) + width = Integer.parseInt(parser.getAttributeValue(null, "width")); + info.setHeight(height); + info.setWidth(width); + } catch (NumberFormatException e) { } + metadata.addInfo(info); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(metadata.getElementName())) { + done = true; + } + } + } + return metadata; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/AvatarProvider.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/AvatarProvider.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,80 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.avatar; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * A PacketExtensionProvider to parse the Avatar data. + * XML namespace urn:xmpp:avatar:data + */ +public class AvatarProvider implements PacketExtensionProvider { + + /** + * Creates a new AvatarProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor + */ + public AvatarProvider() { + } + + @Override + public PacketExtension parseExtension(XmlPullParser parser) + throws Exception { + boolean done = false; + while (!done) { + int eventType = parser.getEventType(); + if (eventType == XmlPullParser.START_TAG) { + if ("data".equals(parser.getName())) { + String data = parser.nextText(); + AvatarExtension avatar = new AvatarExtension(data); + return avatar; + } + } + parser.next(); + } + return null; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/AvatarRetriever.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/AvatarRetriever.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,60 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.avatar; + +import java.io.IOException; + +/** + * Interface for an AvatarRetriever. + */ +public interface AvatarRetriever { + + /** + * Retrieve the avatar. + * + * @return the avatar + * @throws IOException if an IO error occurs while retrieving the avatar + */ + byte[] getAvatar() throws IOException; +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/AvatarRetrieverFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/AvatarRetrieverFactory.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,80 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.avatar; + +import com.beem.project.beem.smack.avatar.AvatarMetadataExtension.Info; +import org.jivesoftware.smack.Connection; +// API level 8 +//import android.net.http.AndroidHttpClient; +//import org.apache.http.client.HttpClient; + +/** + * A factory for AvatarRetriever. + */ +public final class AvatarRetrieverFactory { + + /** + * Private constructor. + */ + private AvatarRetrieverFactory() { + } + + /** + * Get a AvatarRetriever to retrieve this avatar. + * + * @param con the connection + * @param from the user which own the avatar + * @param info the metadata information of the avatar to retrieve + * @return an AvatarRetriever null if none can retrieve this avatar + */ + public static AvatarRetriever getRetriever(Connection con, String from, Info info) { + String url = info.getUrl(); + if (url != null) { + // return new HttpAvatarRetriever(url); + // HttpClient client = AndroidHttpClient.newInstance("Beem"); + return new HttpClientAvatarRetriever(url); + } + return new XmppAvatarRetriever(con, from, info.getId()); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/FileAvatarCache.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/FileAvatarCache.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,121 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.avatar; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * An implementation of an AvatarCache which store the data of the filesystem. + */ +public class FileAvatarCache implements AvatarCache { + + private File mStoreDir; + + /** + * Create a FileAvatarCache. + * + * @param storedir The directory used to store the data. + */ + public FileAvatarCache(final File storedir) { + if (storedir.exists() && !storedir.isDirectory()) + throw new IllegalArgumentException("The store directory must be a directory"); + mStoreDir = storedir; + mStoreDir.mkdirs(); + } + + @Override + public void put(String key, byte[] data) throws IOException { + File f = new File(mStoreDir, key); + OutputStream os = new BufferedOutputStream(new FileOutputStream(f)); + try { + os.write(data); + } finally { + os.close(); + } + } + + @Override + public void put(String key, InputStream in) throws IOException { + File f = new File(mStoreDir, key); + OutputStream os = new BufferedOutputStream(new FileOutputStream(f)); + try { + byte[] data = new byte[1024]; + int nbread; + while ((nbread = in.read(data)) != -1) + os.write(data, 0, nbread); + } finally { + in.close(); + os.close(); + } + } + + @Override + public byte[] get(String key) throws IOException { + File f = new File(mStoreDir, key); + InputStream is = new BufferedInputStream(new FileInputStream(f)); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + byte[] data = new byte[1024]; + is.read(data); + bos.write(data); + } finally { + is.close(); + } + return bos.toByteArray(); + } + + @Override + public boolean contains(String key) { + File f = new File(mStoreDir, key); + return f.exists(); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/HttpAvatarRetriever.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/HttpAvatarRetriever.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,96 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.avatar; + +import java.io.InputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URL; + +/** + * An AvatarRetriever which retrieve the avatar over HTTP. + */ +public class HttpAvatarRetriever implements AvatarRetriever { + + private URL mUrl; + private String mUrlString; + + /** + * Create a HttpAvatarRetriever. + * + * @param url the url of the avatar to download. + */ + public HttpAvatarRetriever(final URL url) { + mUrl = url; + } + + /** + * Create a HttpAvatarRetriever. + * + * @param url the url of the avatar to download. + */ + public HttpAvatarRetriever(final String url) { + mUrlString = url; + } + + @Override + public byte[] getAvatar() throws IOException { + if (mUrl == null) + mUrl = new URL(mUrlString); + InputStream in = mUrl.openStream(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try { + byte[] data = new byte[1024]; + int nbread; + while ((nbread = in.read(data)) != -1) { + os.write(data, 0, nbread); + } + } finally { + in.close(); + os.close(); + } + return os.toByteArray(); + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/HttpClientAvatarRetriever.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/HttpClientAvatarRetriever.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,112 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.avatar; + +import java.io.InputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpEntity; + +/** + * An AvatarRetriever which retrieve the avatar over HTTP using the Apache HttpClient. + */ +public class HttpClientAvatarRetriever implements AvatarRetriever { + + private String mUrl; + private HttpClient mClient; + + /** + * Create a HttpAvatarRetriever. + * + * @param client the custom HttpClient to use to downlowad + * @param url the url of the avatar to download. + */ + public HttpClientAvatarRetriever(final HttpClient client, final String url) { + mUrl = url; + mClient = client; + } + + /** + * Create a HttpAvatarRetriever. + * + * @param url the url of the avatar to download. + */ + public HttpClientAvatarRetriever(final String url) { + mUrl = url; + mClient = new DefaultHttpClient(); + } + + @Override + public byte[] getAvatar() throws IOException { + HttpUriRequest request; + try { + request = new HttpGet(mUrl); + } catch (IllegalArgumentException e) { + IOException ioe = new IOException("Invalid url " + mUrl); + ioe.initCause(e); + throw ioe; + } + HttpResponse response = mClient.execute(request); + HttpEntity entity = response.getEntity(); + InputStream in = entity.getContent(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try { + byte[] data = new byte[1024]; + int nbread; + while ((nbread = in.read(data)) != -1) { + os.write(data, 0, nbread); + } + } finally { + in.close(); + os.close(); + } + return os.toByteArray(); + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/MemoryAvatarCache.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/MemoryAvatarCache.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,85 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + +*/ +package com.beem.project.beem.smack.avatar; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.jivesoftware.smack.util.Cache; + +/** + * An avatar cache which store the avatars in memory. + */ +public class MemoryAvatarCache implements AvatarCache { + private static final int BUFFER_SIZE = 1024; + private Cache mCache; + + /** + * Create a MemoryAvatarCache. + * + * @param maxSize the maximum number of objects the cache will hold. -1 means the cache has no max size. + * @param maxlifetime the maximum amount of time (in ms) objects can exist in cache before being deleted. + * -1 means objects never expire. + */ + public MemoryAvatarCache(final int maxSize, final long maxlifetime) { + mCache = new Cache(maxSize, maxlifetime); + } + + @Override + public void put(String key, byte[] data) throws IOException { + mCache.put(key, data); + } + + @Override + public void put(String key, InputStream in) throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try { + byte[] data = new byte[BUFFER_SIZE]; + int nbread; + while ((nbread = in.read(data)) != -1) + os.write(data, 0, nbread); + } finally { + in.close(); + os.close(); + } + mCache.put(key, os.toByteArray()); + } + + @Override + public byte[] get(String key) throws IOException { + return mCache.get(key); + } + + @Override + public boolean contains(String key) { + return mCache.containsKey(key); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/XmppAvatarRetriever.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/XmppAvatarRetriever.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,92 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.avatar; + +import java.util.List; +import java.util.Arrays; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.pubsub.LeafNode; +import org.jivesoftware.smackx.pubsub.Item; +import org.jivesoftware.smackx.pubsub.PayloadItem; + +import com.beem.project.beem.smack.pep.PepSubManager; + +/** + * An AvatarRetriever which retrieve the avatar over the XMPP connection. + */ +public class XmppAvatarRetriever implements AvatarRetriever { + + private static String AVATARDATANODE = "urn:xmpp:avatar:data"; + private PepSubManager mPubsub; + private String mFrom; + private String mId; + + /** + * Create an XmppAvatarRetriever. + * + * @param con the xmpp connection + * @param from the contact from which we retrieve the avatar + * @param id the id of the avatar to retrieve + */ + public XmppAvatarRetriever(final Connection con, final String from, final String id) { + mPubsub = new PepSubManager(con, from); + mFrom = from; + mId = id; + } + + @Override + public byte[] getAvatar() { + try { + LeafNode node = mPubsub.getPEPNode(AVATARDATANODE); + List items = node.getItems(Arrays.asList(mId)); + PayloadItem item = (PayloadItem) items.get(0); + AvatarExtension avatar = item.getPayload(); + return avatar.getData(); + } catch (XMPPException e) { + return null; + } + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/avatar/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/avatar/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,48 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ + +/** + * This package contains implementation of XEP-0084 User Avatar. + */ +package com.beem.project.beem.smack.avatar; diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/pep/PEPListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/pep/PEPListener.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,63 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.pep; + +import org.jivesoftware.smackx.pubsub.Item; +import java.util.List; + +/** + * A listener that is fired anytime a PEP event message is received. + */ +public interface PEPListener { + + /** + * Called when PEP events are received. + * + * @param from the JID of the user who send the event + * @param node the node of the items in the event + * @param items the different items of the event + */ + void eventReceived(String from, String node, List items); + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/pep/PepSubManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/pep/PepSubManager.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,158 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.smack.pep; + +import java.util.ArrayList; +import java.util.List; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.filter.PacketExtensionFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.pubsub.EventElement; +import org.jivesoftware.smackx.pubsub.EventElementType; +import org.jivesoftware.smackx.pubsub.Item; +import org.jivesoftware.smackx.pubsub.ItemsExtension; +import org.jivesoftware.smackx.pubsub.PubSubManager; +import org.jivesoftware.smackx.pubsub.LeafNode; + +/** + * Little extension of {@link PubSubManager} which allows to add {@link PEPListener}. + * + */ +public class PepSubManager extends PubSubManager { + private List mPepListeners = new ArrayList(); + private PacketFilter mPacketFilter = new PacketExtensionFilter("event", "http://jabber.org/protocol/pubsub#event"); + + /** + * Create a PepSubManager. + * + * @param connection the connection + */ + public PepSubManager(final Connection connection) { + super(connection); + init(connection); + } + + /** + * Create a PepSubManager associated to the specified connection where the pubsub + * requests require a specific to address for packets. + * + * @param connection the connection + * @param toAddress The pubsub specific to address (required for some servers) + */ + public PepSubManager(final Connection connection, final String toAddress) { + super(connection, toAddress); + init(connection); + } + + /** + * Add a listener to PEP event. + * + * @param listener the listener + */ + public void addPEPListener(PEPListener listener) { + if (!mPepListeners.contains(listener)) + mPepListeners.add(listener); + } + + /** + * Remove a listener to PEP event. + * + * @param listener the listener + */ + public void removePEPListener(PEPListener listener) { + mPepListeners.remove(listener); + } + + /** + * Get a PepNode. + * This node is obtain without checking its existence as PEP should auto create it. + * + * @param nodeName the node name + * @return the node + */ + public LeafNode getPEPNode(String nodeName) { + LeafNode node = new LeafNode(con, nodeName); + node.setTo(to); + return node; + } + + /** + * Initialize the PepSubManager. + * + * @param con the connection + */ + private void init(Connection con) { + PacketListener packetListener = new PacketListener() { + + @Override + public void processPacket(Packet packet) { + EventElement e = (EventElement) packet.getExtension("event", "http://jabber.org/protocol/pubsub#event"); + if (e.getEventType() != EventElementType.items) + return; + ItemsExtension it = (ItemsExtension) e.getEvent(); + if (it.getItemsElementType() != ItemsExtension.ItemsElementType.items) + return; + List items = (List) it.getItems(); + firePEPListeners(packet.getFrom(), it.getNode(), items); + } + }; + con.addPacketListener(packetListener, mPacketFilter); + } + + /** + * Fire the PEP listeners. + * + * @param from the JID of the user who send the event + * @param node the node of the items in the event + * @param items the different items of the event + */ + private void firePEPListeners(String from, String node, List items) { + for (PEPListener listener : mPepListeners) { + listener.eventReceived(from, node, items); + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/pep/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/pep/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,49 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ + +/** + * This package contains an implementation of XEP-0163 Personnal Eventing Protocol based on the pubsub implementation. + */ +package com.beem.project.beem.smack.pep; + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/ping/PingExtension.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/ping/PingExtension.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,61 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + +*/ + +package com.beem.project.beem.smack.ping; + +import org.jivesoftware.smack.packet.IQ; + +/** + * This extension represents a iq ping. + * + */ +public class PingExtension extends IQ { + + /** Namespace of the Ping XEP. */ + public static final String NAMESPACE = "urn:xmpp:ping"; + + /** Xml element name for the ping. */ + public static final String ELEMENT = "ping"; + + + /** + * Create a ping iq packet. + */ + public PingExtension() { + } + + @Override + public String getChildElementXML() { + if (getType() == IQ.Type.RESULT) + return null; + return "<" + ELEMENT + " xmlns=\"" + NAMESPACE + "\" />"; + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/ping/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/ping/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,34 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + +*/ + +/** + * This package contains implementation of XEP-0199 XMPP Ping. + */ +package com.beem.project.beem.smack.ping; diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/sasl/SASLGoogleOAuth2Mechanism.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/sasl/SASLGoogleOAuth2Mechanism.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,141 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2012 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ +package com.beem.project.beem.smack.sasl; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import de.measite.smack.Sasl; + +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.sasl.SaslException; +import org.jivesoftware.smack.SASLAuthentication; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.sasl.SASLMechanism; +import org.jivesoftware.smack.util.Base64; + +/** + * An implementation of the SASL OAuth2 mechanism made by Google. + * The extension is described here : + * https://developers.google.com/talk/jep_extensions/oauth + */ +public class SASLGoogleOAuth2Mechanism extends SASLMechanism { + + /** + * The name of the Google Oauth mechanism. + */ + public static final String MECHANISM_NAME = "X-OAUTH2"; + + /** + * Create a SASLGoogleOAuth2Mechanism. + * + * @param saslAuthentication the smack SASLAuthentication. + * + */ + public SASLGoogleOAuth2Mechanism(final SASLAuthentication saslAuthentication) { + super(saslAuthentication); + } + + @Override + public void authenticate(String username, String host, String password) throws IOException, XMPPException { + //Since we were not provided with a CallbackHandler, we will use our own with the given + //information + + //Set the authenticationID as the username, since they must be the same in this case. + this.authenticationId = username; + this.password = password; + this.hostname = host; + + String[] mechanisms = {"PLAIN" }; + Map props = new HashMap(); + sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, this); + authenticate(); + } + + @Override + public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, XMPPException { + String[] mechanisms = {"PLAIN" }; + Map props = new HashMap(); + sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, cbh); + authenticate(); + } + + @Override + protected void authenticate() throws IOException, XMPPException { + String authenticationText = null; + try { + if (sc.hasInitialResponse()) { + byte[] response = sc.evaluateChallenge(new byte[0]); + authenticationText = Base64.encodeBytes(response, Base64.DONT_BREAK_LINES); + } + } catch (SaslException e) { + throw new XMPPException("SASL authentication failed", e); + } + + // Send the authentication to the server + getSASLAuthentication().send(new GoogleOAuthMechanism(authenticationText)); + } + + @Override + protected String getName() { + return MECHANISM_NAME; + } + + /** + * Initiating SASL authentication by select a mechanism. + */ + public static class GoogleOAuthMechanism extends Packet { + private final String authenticationText; + + /** + * Create a GoogleOAuthMechanism. + * + * @param authenticationText the authentification token + * + */ + public GoogleOAuthMechanism(final String authenticationText) { + this.authenticationText = authenticationText; + } + + @Override + public String toXML() { + StringBuilder stanza = new StringBuilder(); + stanza.append(""); + if (authenticationText != null + && authenticationText.trim().length() > 0) { + stanza.append(authenticationText); + } + stanza.append(""); + return stanza.toString(); + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/sasl/ScramSaslClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/sasl/ScramSaslClient.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,302 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2012 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ +package com.beem.project.beem.smack.sasl; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.sasl.SCRAMSHA1ClientAuthenticator; + +import org.apache.harmony.javax.security.auth.callback.Callback; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.auth.callback.NameCallback; +import org.apache.harmony.javax.security.auth.callback.PasswordCallback; +import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; +import org.apache.harmony.javax.security.sasl.SaslClient; +import org.apache.harmony.javax.security.sasl.SaslException; + +/** + * A SaslClient which uses the SCRAM-SHA-! mechanism. + * This implementation is based on Stroke (http://swift.im/git/stroke) + */ +public class ScramSaslClient implements SaslClient { + + private static final int NONCE_BYTE_COUNT = 32; + private static final int NONCE_HEX_COUNT = 2 * NONCE_BYTE_COUNT; + private static final char[] hexChars = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + private SCRAMSHA1ClientAuthenticator clientAuthenticator; + private CallbackHandler cbh; + private String authzid; + + /** + * Create a ScramSaslClient. + * @param authorizationId the authorizationId uses by the client + * @param cbh a CallbackHandler to get more informations + */ + public ScramSaslClient(final String authorizationId, final CallbackHandler cbh) { + this.cbh = cbh; + this.authzid = authorizationId; + } + + /** + * Disposes of any system resources or security-sensitive information the + * SaslClient might be using. Invoking this method invalidates the + * SaslClient instance. This method is idempotent. + * + * @exception SaslException if a problem was encountered while disposing + * of the resources + */ + @Override + public void dispose() throws SaslException { + } + + /** + * Evaluates the challenge data and generates a response. If a challenge + * is received from the server during the authentication process, this + * method is called to prepare an appropriate next response to submit to + * the server. + * + * @param challenge The non-null challenge sent from the server. The + * challenge array may have zero length. + * + * @return The possibly null reponse to send to the server. It is null + * if the challenge accompanied a "SUCCESS" status and the + * challenge only contains data for the client to update its + * state and no response needs to be sent to the server. + * The response is a zero-length byte array if the client is to + * send a response with no data. + * + * @exception SaslException If an error occurred while processing the + * challenge or generating a response. + */ + @Override + public byte[] evaluateChallenge(byte[] challenge) throws SaslException { + if (clientAuthenticator == null) { + Object[] userInfo = getUserInfo(); + String authcid = (String) userInfo[0]; + byte[] passwdBytes = (byte[]) userInfo[1]; + String passwd = new String(passwdBytes); + String nonce = getClientNonce(); + clientAuthenticator = new SCRAMSHA1ClientAuthenticator(nonce); + // some servers (ejabberd) reject the challenge if the + // scram attributes a (authzid) and n (authcid) are equals + // or they just don't handle the authzid + // So we just don't send it if they are the same + if (authcid != null && authcid.equals(authzid)) + authzid = ""; + clientAuthenticator.setCredentials(authcid, passwd, authzid); + return clientAuthenticator.getResponse().getData(); + } + clientAuthenticator.setChallenge(new ByteArray(challenge)); + return clientAuthenticator.getResponse().getData(); + } + + /** + * Returns the IANA-registered mechanism name of this SASL client. + * (e.g. "CRAM-MD5", "GSSAPI") + * + * @return "SCRAM-SHA-!" the IANA-registered mechanism name of this SASL + * client. + */ + @Override + public String getMechanismName() { + return "SCRAM-SHA-1"; + } + + /** + * Retrieves the negotiated property. This method can be called only after + * the authentication exchange has completed (i.e., when isComplete() + * returns true); otherwise, an IllegalStateException is thrown. + * + * + * @param propName The non-null property name + * + * @return The value of the negotiated property. If null, the property was + * not negotiated or is not applicable to this mechanism. This + * implementation allways returns null. + * + * @exception IllegalStateException if this authentication exchange has + * not completed + */ + @Override + public Object getNegotiatedProperty(String propName) { + return null; + } + + /** + * Determines if this mechanism has an optional initial response. If true, + * caller should call evaluateChallenge() with an empty array to get the + * initial response. + * + * @return true if this mechanism has an initial response. This + * implementation always return true. + */ + @Override + public boolean hasInitialResponse() { + return true; + } + + /** + * Determines if the authentication exchange has completed. This method + * may be called at any time, but typically, it will not be called until + * the caller has received indication from the server (in a protocol- + * specific manner) that the exchange has completed. + * + * @return true if the authentication exchange has completed; + * false otherwise. + */ + @Override + public boolean isComplete() { + if (clientAuthenticator == null) + return false; + return clientAuthenticator.getResponse() == null; + } + + /** + * Unwraps a byte array received from the server. This method can be called + * only after the authentication exchange has completed (i.e., when + * isComplete() returns true) and only if the authentication exchange has + * negotiated integrity and/or privacy as the quality of protection; + * otherwise, an IllegalStateException is thrown. + * + * This implementation always throw an IllegalStateException. + * + * incoming is the contents of the SASL buffer as defined in RFC 2222 + * without the leading four octet field that represents the length. + * offset and len specify the portion of incoming to use. + * + * @param incoming A non-null byte array containing the encoded bytes + * from the server + * @param offset The starting position at incoming of the bytes to use + * + * @param len The number of bytes from incoming to use + * + * @return A non-null byte array containing the decoded bytes + * @throws SaslException if an error occurs + * + */ + @Override + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException { + throw new IllegalStateException("SCRAM-SHA-1: this mechanism supports " + + "neither integrity nor privacy"); + } + + /** + * Wraps a byte array to be sent to the server. This method can be called + * only after the authentication exchange has completed (i.e., when + * isComplete() returns true) and only if the authentication exchange has + * negotiated integrity and/or privacy as the quality of protection; + * otherwise, an IllegalStateException is thrown. + * + * This implementation always throw an IllegalStateException. + * + * The result of this method will make up the contents of the SASL buffer as + * defined in RFC 2222 without the leading four octet field that represents + * the length. offset and len specify the portion of outgoing to use. + * + * @param outgoing A non-null byte array containing the bytes to encode + * @param offset The starting position at outgoing of the bytes to use + * @param len The number of bytes from outgoing to use + * + * @return A non-null byte array containing the encoded bytes + * + * @exception SaslException if incoming cannot be successfully unwrapped. + * + * @exception IllegalStateException if the authentication exchange has + * not completed, or if the negotiated quality of + * protection has neither integrity nor privacy. + */ + @Override + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException { + throw new IllegalStateException("SCRAM-SHA-1: this mechanism supports " + + "neither integrity nor privacy"); + } + + + /** + * Calculates the Nonce value of the Client. + * + * @return Nonce value of the client + * + * @exception SaslException If an error Occurs + */ + private String getClientNonce() throws SaslException { + byte[] nonceBytes = new byte[NONCE_BYTE_COUNT]; + SecureRandom prng; + char[] hexNonce = new char[NONCE_HEX_COUNT]; + + try { + prng = SecureRandom.getInstance("SHA1PRNG"); + prng.nextBytes(nonceBytes); + for (int i = 0; i < NONCE_BYTE_COUNT; i++) { + final int val = nonceBytes[i] & 0xff; + //low nibble + hexNonce[i * 2] = hexChars[val / 0x10]; + //high nibble + hexNonce[(i * 2) + 1] = hexChars[val % 0x10]; + } + return new String(hexNonce); + } catch (NoSuchAlgorithmException e) { + throw new SaslException("No random number generator available", e); + } + } + + /** + * Get informations supplied by the user of the SaslClient. + * These informations are retrived by using the CallbackHandler. + * + * @return an array of object + * @throws SaslException if the informations cannot be retrieved + */ + private Object[] getUserInfo() throws SaslException { + try { + final String userPrompt = "Authentication id: "; + final String pwPrompt = "Password: "; + NameCallback nameCb = new NameCallback(userPrompt); + PasswordCallback passwordCb = new PasswordCallback(pwPrompt, false); + cbh.handle(new Callback[] {nameCb, passwordCb }); + String userid = nameCb.getName(); + char[] pwchars = passwordCb.getPassword(); + byte[] pwbytes; + if (pwchars != null) { + pwbytes = (new String(pwchars)).getBytes("UTF8"); + passwordCb.clearPassword(); + } else { + pwbytes = null; + } + return new Object[] {userid, pwbytes }; + } catch (IOException e) { + throw new SaslException("Cannot get password", e); + } catch (UnsupportedCallbackException e) { + throw new SaslException("Cannot get userid/password", e); + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/sasl/ScramSaslMechanism.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/sasl/ScramSaslMechanism.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,81 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2012 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ +package com.beem.project.beem.smack.sasl; + +import java.io.IOException; + + +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.jivesoftware.smack.SASLAuthentication; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.sasl.SASLMechanism; + +/** + * An implementation of the SCRAM-SHA-1 SASL mechanism. + * This implementation is based on Stroke http://swift.im/git/stroke. + */ +public class ScramSaslMechanism extends SASLMechanism { + + /** + * The name of the SASL mechanism. + */ + public static final String MECHANISM_NAME = "SCRAM-SHA-1"; + + /** + * Create a ScramSaslMechanism. + * + * @param saslAuthentication the smack SASLAuthentication. + * + */ + public ScramSaslMechanism(final SASLAuthentication saslAuthentication) { + super(saslAuthentication); + } + + @Override + public void authenticate(String username, String host, String password) throws IOException, XMPPException { + this.authenticationId = username; + this.password = password; + this.hostname = host; + + sc = new ScramSaslClient(username, this); + authenticate(); + } + + @Override + public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, XMPPException { + this.authenticationId = username; + this.hostname = host; + sc = new ScramSaslClient(username, cbh); + authenticate(); + } + + @Override + protected String getName() { + return MECHANISM_NAME; + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/smack/sasl/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/smack/sasl/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,31 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2012 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ + +/** + * This package contains implementations of different SASL mechanism. + */ +package com.beem.project.beem.smack.sasl; diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/AddContact.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/AddContact.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,223 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui; + +import java.util.ArrayList; +import java.util.List; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import com.beem.project.beem.BeemService; +import com.beem.project.beem.R; +import com.beem.project.beem.service.aidl.IRoster; +import com.beem.project.beem.service.aidl.IXmppFacade; +import com.beem.project.beem.utils.BeemBroadcastReceiver; + +/** + * This activity is used to add a contact. + * @author nikita + */ +public class AddContact extends Activity { + + private static final Intent SERVICE_INTENT = new Intent(); + private static final String TAG = "AddContact"; + private final List mGroup = new ArrayList(); + private IXmppFacade mXmppFacade; + private final ServiceConnection mServConn = new BeemServiceConnection(); + private final BeemBroadcastReceiver mReceiver = new BeemBroadcastReceiver(); + private final OkListener mOkListener = new OkListener(); + + static { + SERVICE_INTENT.setComponent(new ComponentName("com.beem.project.beem", "com.beem.project.beem.BeemService")); + } + + /** + * Constructor. + */ + public AddContact() { + } + + /** + * {@inheritDoc} + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.addcontact); + Button ok = (Button) findViewById(R.id.addc_ok); + ok.setOnClickListener(mOkListener); + this.registerReceiver(mReceiver, new IntentFilter(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED)); + } + + /* (non-Javadoc) + * @see android.app.Activity#onStart() + */ + @Override + protected void onStart() { + super.onStart(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onResume() { + super.onResume(); + bindService(new Intent(this, BeemService.class), mServConn, BIND_AUTO_CREATE); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPause() { + super.onPause(); + unbindService(mServConn); + } + + /* (non-Javadoc) + * @see android.app.Activity#onDestroy() + */ + @Override + protected void onDestroy() { + super.onDestroy(); + this.unregisterReceiver(mReceiver); + } + + /** + * Get the text of a widget. + * @param id the id of the widget. + * @return the text of the widget. + */ + private String getWidgetText(int id) { + EditText widget = (EditText) this.findViewById(id); + return widget.getText().toString(); + } + + /** + * The ServiceConnection used to connect to the Beem service. + */ + private class BeemServiceConnection implements ServiceConnection { + + /** + * Constructor. + */ + public BeemServiceConnection() { + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mXmppFacade = IXmppFacade.Stub.asInterface(service); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mXmppFacade = null; + } + } + + /** + * Listener. + */ + private class OkListener implements OnClickListener { + + /** + * Constructor. + */ + public OkListener() { } + + @Override + public void onClick(View v) { + String login; + login = getWidgetText(R.id.addc_login); + if (TextUtils.isEmpty(login)) { + Toast.makeText(AddContact.this, getString(R.string.AddCContactAddedLoginError), Toast.LENGTH_SHORT) + .show(); + return; + } + String alias; + alias = getWidgetText(R.id.addc_alias); + if (getWidgetText(R.id.addc_group).length() != 0) + mGroup.add(getWidgetText(R.id.addc_group)); + try { + if (mXmppFacade != null) { + IRoster roster = mXmppFacade.getRoster(); + if (roster.getContact(login) != null) { + mGroup.addAll(roster.getContact(login).getGroups()); + Toast.makeText(AddContact.this, getString(R.string.AddCContactAlready), Toast.LENGTH_SHORT) + .show(); + return; + } + if (!roster.addContact(login, alias, mGroup.toArray(new String[mGroup.size()]))) { + Toast.makeText(AddContact.this, getString(R.string.AddCContactAddedError), Toast.LENGTH_SHORT) + .show(); + return; + } else { + Toast.makeText(AddContact.this, getString(R.string.AddCContactAdded), Toast.LENGTH_SHORT) + .show(); + finish(); + } + } + } catch (RemoteException e) { + Toast.makeText(AddContact.this, e.getMessage(), Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Problem adding contact", e); + } + + } + }; +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/ChangeStatus.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/ChangeStatus.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,602 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ + +package com.beem.project.beem.ui; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.IBinder; +import android.os.RemoteException; +import android.preference.PreferenceManager; +import android.provider.MediaStore; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ListAdapter; +import android.widget.Spinner; +import android.widget.Toast; + +import com.beem.project.beem.BeemApplication; +import com.beem.project.beem.BeemService; +import com.beem.project.beem.R; +import com.beem.project.beem.providers.AvatarProvider; +import com.beem.project.beem.service.UserInfo; +import com.beem.project.beem.service.aidl.IXmppFacade; +import com.beem.project.beem.utils.BeemBroadcastReceiver; +import com.beem.project.beem.utils.BeemConnectivity; +import com.beem.project.beem.utils.Status; + +/** + * This Activity is used to change the status. + * @author nikita + */ +public class ChangeStatus extends Activity { + + private static final Intent SERVICE_INTENT = new Intent(); + static { + SERVICE_INTENT.setComponent(new ComponentName("com.beem.project.beem", "com.beem.project.beem.BeemService")); + } + + private static final String TAG = ChangeStatus.class.getSimpleName(); + private static final int AVAILABLE_FOR_CHAT_IDX = 0; + private static final int AVAILABLE_IDX = 1; + private static final int BUSY_IDX = 2; + private static final int AWAY_IDX = 3; + private static final int UNAVAILABLE_IDX = 4; + private static final int DISCONNECTED_IDX = 5; + + private static final int ICON_SIZE = 80; + + private static final int SELECT_PHOTO_DLG = 0; + + private static final int CAMERA_WITH_DATA = 0; + private static final int PHOTO_PICKED_WITH_DATA = 1; + + private static final File PHOTO_DIR = new File( + Environment.getExternalStorageDirectory() + "/DCIM/Camera"); + + private static final String KEY_CURRENT_PHOTO_FILE = "currentphotofile"; + + private static final Uri MY_AVATAR_URI = Uri.parse(AvatarProvider.CONTENT_URI + "/my_avatar"); + + private EditText mStatusMessageEditText; + private Toast mToast; + private Button mOk; + private Button mClear; + private Button mContact; + private Spinner mSpinner; + private ImageButton mAvatar; + private Uri mAvatarUri; + + private SharedPreferences mSettings; + private ArrayAdapter mAdapter; + private IXmppFacade mXmppFacade; + private final ServiceConnection mServConn = new BeemServiceConnection(); + private final OnClickListener mOnClickOk = new MyOnClickListener(); + private final BeemBroadcastReceiver mReceiver = new BeemBroadcastReceiver(); + private boolean mShowCurrentAvatar = true; + private boolean mDisableAvatar; + private File mCurrentPhotoFile; + + /** + * Constructor. + */ + public ChangeStatus() { + } + + /** + * {@inheritDoc} + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Log.d(TAG, "oncreate"); + setContentView(R.layout.changestatus); + + mOk = (Button) findViewById(R.id.ChangeStatusOk); + mOk.setOnClickListener(mOnClickOk); + + mClear = (Button) findViewById(R.id.ChangeStatusClear); + mClear.setOnClickListener(mOnClickOk); + + mContact = (Button) findViewById(R.id.OpenContactList); + mContact.setOnClickListener(mOnClickOk); + + BeemApplication app = (BeemApplication) getApplication(); + mAvatar = (ImageButton) findViewById(R.id.avatarButton); + mAvatar.setOnClickListener(mOnClickOk); + if (!app.isPepEnabled()) { + View avatarPanel = findViewById(R.id.avatar_panel); + avatarPanel.setVisibility(View.GONE); + } + + + mSettings = PreferenceManager.getDefaultSharedPreferences(this); + mStatusMessageEditText = (EditText) findViewById(R.id.ChangeStatusMessage); + mStatusMessageEditText.setText(mSettings.getString(BeemApplication.STATUS_TEXT_KEY, "")); + + mSpinner = (Spinner) findViewById(R.id.ChangeStatusSpinner); + mAdapter = ArrayAdapter.createFromResource(this, R.array.status_types, android.R.layout.simple_spinner_item); + mAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mSpinner.setAdapter(mAdapter); + + mToast = Toast.makeText(this, R.string.ChangeStatusOk, Toast.LENGTH_LONG); + mSpinner.setSelection(getPreferenceStatusIndex()); + + this.registerReceiver(mReceiver, new IntentFilter(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED)); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onResume() { + super.onResume(); + if (!BeemConnectivity.isConnected(getApplicationContext())) { + Intent i = new Intent(this, Login.class); + startActivity(i); + finish(); + } + bindService(new Intent(this, BeemService.class), mServConn, BIND_AUTO_CREATE); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPause() { + super.onPause(); + unbindService(mServConn); + } + + /* (non-Javadoc) + * @see android.app.Activity#onDestroy() + */ + @Override + protected void onDestroy() { + super.onDestroy(); + this.unregisterReceiver(mReceiver); + } + + /* + * The activity is often reclaimed by the system memory. + */ + @Override + protected void onSaveInstanceState(Bundle outState) { + if (mCurrentPhotoFile != null) { + outState.putString(KEY_CURRENT_PHOTO_FILE, mCurrentPhotoFile.toString()); + } + super.onSaveInstanceState(outState); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + String fileName = savedInstanceState.getString(KEY_CURRENT_PHOTO_FILE); + if (fileName != null) { + mCurrentPhotoFile = new File(fileName); + } + super.onRestoreInstanceState(savedInstanceState); + } + + + + @Override + protected Dialog onCreateDialog(int id) { + if (id == SELECT_PHOTO_DLG) + return createPickPhotoDialog(); + return null; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // Ignore failed requests + if (resultCode != RESULT_OK) return; + + switch (requestCode) { + case PHOTO_PICKED_WITH_DATA: + // We tell the activity to put the result in MY_AVATAR_URI + mAvatarUri = MY_AVATAR_URI; + Log.d(TAG, "selected avatar uri " + mAvatarUri); + if (mAvatarUri != null) { + // force reload of image even it is the same uri + mAvatar.setImageURI(null); + mAvatar.setImageURI(mAvatarUri); + mDisableAvatar = false; + mShowCurrentAvatar = false; + } + break; + + case CAMERA_WITH_DATA: + doCropPhoto(mCurrentPhotoFile); + break; + default: + Log.w(TAG, "onActivityResult : invalid request code"); + + } + } + + /** + * Return the status index from status the settings. + * @return the status index from status the settings. + */ + private int getPreferenceStatusIndex() { + return mSettings.getInt(BeemApplication.STATUS_KEY, AVAILABLE_IDX); + } + + /** + * Return the status text from status the settings. + * @param id status text id. + * @return the status text from status the settings. + */ + private String getPreferenceString(int id) { + return mSettings.getString(getString(id), ""); + } + + /** + * convert status text to. + * @param item selected item text. + * @return item position in the array. + */ + private int getStatusForService(String item) { + int result; + switch (mAdapter.getPosition(item)) { + case ChangeStatus.DISCONNECTED_IDX: + result = Status.CONTACT_STATUS_DISCONNECT; + break; + case ChangeStatus.AVAILABLE_FOR_CHAT_IDX: + result = Status.CONTACT_STATUS_AVAILABLE_FOR_CHAT; + break; + case ChangeStatus.AVAILABLE_IDX: + result = Status.CONTACT_STATUS_AVAILABLE; + break; + case ChangeStatus.AWAY_IDX: + result = Status.CONTACT_STATUS_AWAY; + break; + case ChangeStatus.BUSY_IDX: + result = Status.CONTACT_STATUS_BUSY; + break; + case ChangeStatus.UNAVAILABLE_IDX: + result = Status.CONTACT_STATUS_UNAVAILABLE; + break; + default: + result = Status.CONTACT_STATUS_AVAILABLE; + break; + } + return result; + } + + /** + * ClickListener for the avatarButton. + * + * @param button the avatar button + */ + private void onAvatarButton(View button) { + showDialog(SELECT_PHOTO_DLG); + } + + /** + * Publish the selected avatar. + */ + private void publishAvatar() { + try { + if (mDisableAvatar) + mXmppFacade.disableAvatarPublishing(); + else if (mAvatarUri != null) + mXmppFacade.publishAvatar(mAvatarUri); + } catch (RemoteException e) { + Log.e(TAG, "Error while publishing avatar", e); + } + } + + /** + * Display the current avatar in the button. + */ + private void displayCurrentAvatar() { + try { + UserInfo ui = mXmppFacade.getUserInfo(); + if (ui == null) + return; + String avatarId = ui.getAvatarId(); + Log.d(TAG, "User info : avatar id " + avatarId); + if (avatarId != null) { + Uri uri = AvatarProvider.CONTENT_URI.buildUpon().appendPath(avatarId).build(); + mAvatar.setImageURI(uri); + } + } catch (RemoteException e) { + Log.e(TAG, "Error while displaying current avatar", e); + } + mShowCurrentAvatar = false; + } + + /* + * Some codes from AOSP (platform/packages/apps/Contacts) + * to select and crop an image. + */ + + /** + * Creates a dialog offering two options: take a photo or pick a photo from the gallery. + * @return the dialog + */ + private Dialog createPickPhotoDialog() { + // Wrap our context to inflate list items using correct theme + final Context dialogContext = new ContextThemeWrapper(this, + android.R.style.Theme_Light); + + final ListAdapter adapter = ArrayAdapter.createFromResource(dialogContext, + R.array.pick_photo_items, + android.R.layout.simple_list_item_1); + + final AlertDialog.Builder builder = new AlertDialog.Builder(dialogContext); + builder.setTitle(R.string.select_avatar); + builder.setSingleChoiceItems(adapter, -1, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + switch(which) { + case 0: + doTakePhoto(); + break; + case 1: + doPickPhotoFromGallery(); + break; + case 2: + mDisableAvatar = true; + mAvatar.setImageURI(null); + break; + default: + Log.w(TAG, "DialogInterface onClick : invalid which code"); + } + } + }); + return builder.create(); + } + + /** + * Create a file name for the icon photo using current time. + * @return the filename + */ + private String getPhotoFileName() { + Date date = new Date(System.currentTimeMillis()); + SimpleDateFormat dateFormat = new SimpleDateFormat("'IMG'_yyyyMMdd_HHmmss"); + return dateFormat.format(date) + ".jpg"; + } + + /** + * Launches Camera to take a picture and store it in a file. + */ + protected void doTakePhoto() { + try { + // Launch camera to take photo for selected contact + PHOTO_DIR.mkdirs(); + mCurrentPhotoFile = new File(PHOTO_DIR, getPhotoFileName()); + final Intent intent = getTakePickIntent(mCurrentPhotoFile); + startActivityForResult(intent, CAMERA_WITH_DATA); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show(); + } + } + + /** + * Constructs an intent for capturing a photo and storing it in a temporary file. + * @param f the temporary file to use to store the picture + * @return the intent + */ + public static Intent getTakePickIntent(File f) { + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE, null); + intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f)); + return intent; + } + + /** + * Sends a newly acquired photo to Gallery for cropping. + * @param f the image file to crop + */ + protected void doCropPhoto(final File f) { + try { + + // Add the image to the media store + // level 8 + /* + MediaScannerConnection.scanFile( + this, + new String[] { f.getAbsolutePath() }, + new String[] { null }, + null); + */ + + // Launch gallery to crop the photo + final Intent intent = getCropImageIntent(Uri.fromFile(f)); + startActivityForResult(intent, PHOTO_PICKED_WITH_DATA); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "Cannot crop image", e); + Toast.makeText(this, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show(); + } + } + + /** + * Constructs an intent for image cropping. + * @param photoUri the uri of the photo to crop + * @return the intent + */ + public static Intent getCropImageIntent(Uri photoUri) { + Intent intent = new Intent("com.android.camera.action.CROP"); + intent.setDataAndType(photoUri, "image/*"); + intent.putExtra("crop", "true"); + intent.putExtra("aspectX", 1); + intent.putExtra("aspectY", 1); + intent.putExtra("outputX", ICON_SIZE); + intent.putExtra("outputY", ICON_SIZE); + intent.putExtra(MediaStore.EXTRA_OUTPUT, MY_AVATAR_URI); + return intent; + } + + /** + * Launches Gallery to pick a photo. + */ + protected void doPickPhotoFromGallery() { + try { + // Launch picker to choose photo for selected contact + final Intent intent = getPhotoPickIntent(); + startActivityForResult(intent, PHOTO_PICKED_WITH_DATA); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show(); + } + } + + /** + * Constructs an intent for picking a photo from Gallery, cropping it and returning the bitmap. + * @return the intent + */ + public static Intent getPhotoPickIntent() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null); + intent.setType("image/*"); + intent.putExtra("crop", "true"); + intent.putExtra("aspectX", 1); + intent.putExtra("aspectY", 1); + intent.putExtra("outputX", ICON_SIZE); + intent.putExtra("outputY", ICON_SIZE); + intent.putExtra(MediaStore.EXTRA_OUTPUT, MY_AVATAR_URI); + // use this to get the bitmap in the intent +// intent.putExtra("return-data", true); + return intent; + } + + + + /** + * connection to service. + * @author nikita + */ + private class BeemServiceConnection implements ServiceConnection { + + /** + * constructor. + */ + public BeemServiceConnection() { + } + + /** + * {@inheritDoc} + */ + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mXmppFacade = IXmppFacade.Stub.asInterface(service); + if (mShowCurrentAvatar) + displayCurrentAvatar(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onServiceDisconnected(ComponentName name) { + mXmppFacade = null; + } + } + + /** + * User have clicked on ok. + * @author nikita + */ + private class MyOnClickListener implements OnClickListener { + + /** + * constructor. + */ + public MyOnClickListener() { + } + + @Override + public void onClick(View v) { + if (v == mOk) { + String msg = mStatusMessageEditText.getText().toString(); + int status = getStatusForService((String) mSpinner.getSelectedItem()); + Editor edit = mSettings.edit(); + edit.putString(BeemApplication.STATUS_TEXT_KEY, msg); + if (status == Status.CONTACT_STATUS_DISCONNECT) { + stopService(new Intent(ChangeStatus.this, BeemService.class)); + } else { + try { + mXmppFacade.changeStatus(status, msg.toString()); + edit.putInt(BeemApplication.STATUS_KEY, mSpinner.getSelectedItemPosition()); + publishAvatar(); + } catch (RemoteException e) { + e.printStackTrace(); + } + mToast.show(); + } + edit.commit(); + ChangeStatus.this.finish(); + } else if (v == mClear) { + mStatusMessageEditText.setText(null); + } else if (v == mContact) { + startActivity(new Intent(ChangeStatus.this, ContactList.class)); + ChangeStatus.this.finish(); + } else if (v == mAvatar) + onAvatarButton(v); + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/Chat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/Chat.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,1049 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui; + +import java.io.IOException; +import java.io.InputStream; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.app.Activity; +import android.app.Dialog; +import android.content.ComponentName; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.preference.PreferenceManager; +import android.text.util.Linkify; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.widget.BaseAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import com.android.mms.util.SmileyParser; + +import com.beem.project.beem.BeemApplication; +import com.beem.project.beem.R; +import com.beem.project.beem.providers.AvatarProvider; +import com.beem.project.beem.service.Contact; +import com.beem.project.beem.service.Message; +import com.beem.project.beem.service.PresenceAdapter; +import com.beem.project.beem.service.aidl.IBeemRosterListener; +import com.beem.project.beem.service.aidl.IChat; +import com.beem.project.beem.service.aidl.IChatManager; +import com.beem.project.beem.service.aidl.IChatManagerListener; +import com.beem.project.beem.service.aidl.IMessageListener; +import com.beem.project.beem.service.aidl.IRoster; +import com.beem.project.beem.service.aidl.IXmppFacade; +import com.beem.project.beem.ui.dialogs.builders.ChatList; +import com.beem.project.beem.ui.dialogs.builders.DisplayOtrFingerprint; +import com.beem.project.beem.utils.BeemBroadcastReceiver; +import com.beem.project.beem.utils.Status; + +import org.jivesoftware.smack.packet.Presence.Mode; +import org.jivesoftware.smack.util.StringUtils; + + +/** + * This class represents an activity which allows the user to chat with his/her contacts. + * @author Jean-Manuel Da Silva + */ +public class Chat extends Activity implements TextView.OnEditorActionListener { + + private static final String TAG = "Chat"; + private static final Intent SERVICE_INTENT = new Intent(); + static { + SERVICE_INTENT.setComponent(new ComponentName("com.beem.project.beem", "com.beem.project.beem.BeemService")); + } + private Handler mHandler = new Handler(); + + private IRoster mRoster; + private Contact mContact; + + private TextView mContactNameTextView; + private TextView mContactStatusMsgTextView; + private TextView mContactChatState; + private TextView mContactOtrState; + private ImageView mContactStatusIcon; + private LayerDrawable mAvatarStatusDrawable; + private ListView mMessagesListView; + private EditText mInputField; + private Button mSendButton; + private final Map mStatusIconsMap = new HashMap(); + + private final List mListMessages = new ArrayList(); + + private IChat mChat; + private IChatManager mChatManager; + private final IMessageListener mMessageListener = new OnMessageListener(); + private final IChatManagerListener mChatManagerListener = new ChatManagerListener(); + private MessagesListAdapter mMessagesListAdapter = new MessagesListAdapter(); + + private final ServiceConnection mConn = new BeemServiceConnection(); + private final BeemBroadcastReceiver mBroadcastReceiver = new BeemBroadcastReceiver(); + private final BeemRosterListener mBeemRosterListener = new BeemRosterListener(); + private IXmppFacade mXmppFacade; + private String mCurrentAvatarId; + private boolean mBinded; + private boolean mCompact; + + /** + * Constructor. + */ + public Chat() { + super(); + } + + /** + * {@inheritDoc}. + */ + @Override + protected void onCreate(Bundle savedBundle) { + super.onCreate(savedBundle); + this.registerReceiver(mBroadcastReceiver, new IntentFilter(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED)); + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); + + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); + } + + mCompact = settings.getBoolean(BeemApplication.USE_COMPACT_CHAT_UI_KEY, false); + // UI + if (!mCompact) { + setContentView(R.layout.chat); + mContactNameTextView = (TextView) findViewById(R.id.chat_contact_name); + mContactStatusMsgTextView = (TextView) findViewById(R.id.chat_contact_status_msg); + mContactChatState = (TextView) findViewById(R.id.chat_contact_chat_state); + mContactStatusIcon = (ImageView) findViewById(R.id.chat_contact_status_icon); + mAvatarStatusDrawable = (LayerDrawable) mContactStatusIcon.getDrawable(); + mAvatarStatusDrawable.setLayerInset(1, 36, 36, 0, 0); + } else { + setContentView(R.layout.chat_compact); + } + mContactOtrState = (TextView) findViewById(R.id.chat_contact_otr_state); + mMessagesListView = (ListView) findViewById(R.id.chat_messages); + mMessagesListView.setAdapter(mMessagesListAdapter); + mInputField = (EditText) findViewById(R.id.chat_input); + mInputField.setOnEditorActionListener(this); + mInputField.requestFocus(); + mSendButton = (Button) findViewById(R.id.chat_send_message); + mSendButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + sendMessage(); + } + }); + + prepareIconsStatus(); + } + + @Override + protected void onResume() { + super.onResume(); + mContact = new Contact(getIntent().getData()); + if (!mBinded) { + bindService(SERVICE_INTENT, mConn, BIND_AUTO_CREATE); + mBinded = true; + } + } + + /** + * {@inheritDoc}. + */ + @Override + protected void onDestroy() { + super.onDestroy(); + this.unregisterReceiver(mBroadcastReceiver); + } + + /** + * {@inheritDoc}. + */ + @Override + protected void onPause() { + super.onPause(); + try { + if (mChat != null) { + mChat.setOpen(false); + mChat.removeMessageListener(mMessageListener); + } + if (mRoster != null) + mRoster.removeRosterListener(mBeemRosterListener); + if (mChatManager != null) + mChatManager.removeChatCreationListener(mChatManagerListener); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + if (mBinded) { + unbindService(mConn); + mBinded = false; + } + mXmppFacade = null; + mRoster = null; + mChat = null; + mChatManager = null; + } + + /** + * {@inheritDoc}. + */ + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + } + + /** + * {@inheritDoc}. + */ + @Override + protected void onSaveInstanceState(Bundle savedInstanceState) { + super.onSaveInstanceState(savedInstanceState); + } + + /** + * {@inheritDoc}. + */ + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + } + + /** + * {@inheritDoc}. + */ + @Override + public final boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.chat, menu); + return true; + } + + /** + * {@inheritDoc}. + */ + @Override + public final boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.chat_menu_contacts_list: + Intent contactListIntent = new Intent(this, ContactList.class); + contactListIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(contactListIntent); + break; + case R.id.chat_menu_change_chat: + try { + final List openedChats = mChatManager.getOpenedChatList(); + Dialog chatList = new ChatList(Chat.this, openedChats).create(); + chatList.show(); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + break; + case R.id.chat_menu_close_chat: + try { + mChatManager.destroyChat(mChat); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + this.finish(); + break; + case R.id.chat_menu_start_otr_session: + try { + if (mChat == null) { + mChat = mChatManager.createChat(mContact, mMessageListener); + if (mChat != null) { + mChat.setOpen(true); + } + } + mChat.startOtrSession(); + } catch (RemoteException e) { + Log.e(TAG, "start otr chats failed " + mChat, e); + } + break; + case R.id.chat_menu_stop_otr_session: + try { + if (mChat == null) { + mChat = mChatManager.createChat(mContact, mMessageListener); + if (mChat != null) { + mChat.setOpen(true); + } + } + mChat.endOtrSession(); + } catch (RemoteException e) { + Log.e(TAG, "close otr chats failed " + mChat, e); + } + break; + case R.id.chat_menu_otr_verify_key: + try { + if (mChat == null) { + mChat = mChatManager.createChat(mContact, mMessageListener); + if (mChat != null) { + mChat.setOpen(true); + } + } + Dialog otrDialog = new DisplayOtrFingerprint(this, mChat).create(); + otrDialog.show(); + } catch (RemoteException e) { + Log.e(TAG, "getting local otr key failed " + mChat, e); + } + break; + default: + return false; + } + return true; + } + + /** + * Change the displayed chat. + * @param contact the targeted contact of the new chat + * @throws RemoteException If a Binder remote-invocation error occurred. + */ + private void changeCurrentChat(Contact contact) throws RemoteException { + if (mChat != null) { + mChat.setOpen(false); + mChat.removeMessageListener(mMessageListener); + } + mChat = mChatManager.getChat(contact); + if (mChat != null) { + mChat.setOpen(true); + mChat.addMessageListener(mMessageListener); + mChatManager.deleteChatNotification(mChat); + updateOtrInformations(mChat.getOtrStatus()); + } + mContact = mRoster.getContact(contact.getJID()); + String res = contact.getSelectedRes(); + if (mContact == null) + mContact = contact; + if (!"".equals(res)) { + mContact.setSelectedRes(res); + } + updateContactInformations(); + updateContactStatusIcon(); + + playRegisteredTranscript(); + } + + /** + * Get all messages from the current chat and refresh the activity with them. + * @throws RemoteException If a Binder remote-invocation error occurred. + */ + private void playRegisteredTranscript() throws RemoteException { + mListMessages.clear(); + if (mChat != null) { + List msgList = convertMessagesList(mChat.getMessages()); + mListMessages.addAll(msgList); + mMessagesListAdapter.notifyDataSetChanged(); + } + } + + /** + * Convert a list of Message coming from the service to a list of MessageText that can be displayed in UI. + * @param chatMessages the list of Message + * @return a list of message that can be displayed. + */ + private List convertMessagesList(List chatMessages) { + List result = new ArrayList(chatMessages.size()); + String remoteName = mContact.getName(); + String localName = getString(R.string.chat_self); + MessageText lastMessage = null; + + for (Message m : chatMessages) { + String name = remoteName; + String fromBareJid = StringUtils.parseBareAddress(m.getFrom()); + if (m.getType() == Message.MSG_TYPE_ERROR) { + lastMessage = null; + result.add(new MessageText(fromBareJid, name, m.getBody(), true, m.getTimestamp())); + } else if (m.getType() == Message.MSG_TYPE_INFO) { + lastMessage = new MessageText("", "", m.getBody(), false); + result.add(lastMessage); + + } else if (m.getType() == Message.MSG_TYPE_CHAT) { + if (fromBareJid == null) { //nofrom or from == yours + name = localName; + fromBareJid = ""; + } + + if (m.getBody() != null) { + if (lastMessage == null || !fromBareJid.equals(lastMessage.getBareJid())) { + lastMessage = new MessageText(fromBareJid, name, m.getBody(), false, m.getTimestamp()); + result.add(lastMessage); + } else { + lastMessage.setMessage(lastMessage.getMessage().concat("\n" + m.getBody())); + } + } + } + } + return result; + } + + + /** + * {@inheritDoc}. + */ + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (v == mInputField && actionId == EditorInfo.IME_ACTION_SEND) { + sendMessage(); + return true; + } + return false; + } + + /** + * Send an XMPP message. + */ + private void sendMessage() { + final String inputContent = mInputField.getText().toString(); + + if (!"".equals(inputContent)) { + Message msgToSend = new Message(mContact.getJIDWithRes(), Message.MSG_TYPE_CHAT); + msgToSend.setBody(inputContent); + + try { + if (mChat == null) { + mChat = mChatManager.createChat(mContact, mMessageListener); + mChat.setOpen(true); + } + mChat.sendMessage(msgToSend); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + + final String self = getString(R.string.chat_self); + MessageText lastMessage = null; + if (mListMessages.size() != 0) + lastMessage = mListMessages.get(mListMessages.size() - 1); + + if (lastMessage != null && lastMessage.getName().equals(self)) { + lastMessage.setMessage(lastMessage.getMessage().concat("\n" + inputContent)); + lastMessage.setTimestamp(new Date()); + } else + mListMessages.add(new MessageText(self, self, inputContent, false, new Date())); + mMessagesListAdapter.notifyDataSetChanged(); + mInputField.setText(null); + } + } + + + /** + * Update the contact informations. + */ + private void updateContactInformations() { + // Check for a contact name update + String name = mContact.getName(); + String res = mContact.getSelectedRes(); + if (!"".equals(res)) + name += "(" + res + ")"; + if (!mCompact) { + if (!(mContactNameTextView.getText().toString().equals(name))) + mContactNameTextView.setText(name); + //Check for a contact status message update + if (!(mContactStatusMsgTextView.getText().toString().equals(mContact.getMsgState()))) { + mContactStatusMsgTextView.setText(mContact.getMsgState()); + Linkify.addLinks(mContactStatusMsgTextView, Linkify.WEB_URLS); + } + } else { + Mode m = Status.getPresenceModeFromStatus(mContact.getStatus()); + if (m == null) + setTitle(getString(R.string.chat_name) + " " + name + " (" + + getString(R.string.contact_status_msg_offline) + ")"); + else + setTitle(getString(R.string.chat_name) + " " + name + " (" + m.name() + ")"); + } + } + + /** + * Update the OTR informations. + * @param otrState the otr state + */ + private void updateOtrInformations(final String otrState) { + String text = null; + if ("ENCRYPTED".equals(otrState)) { + text = Chat.this.getString(R.string.chat_otrstate_encrypted); + } else if ("FINISHED".equals(otrState)) { + text = Chat.this.getString(R.string.chat_otrstate_finished); + } else if ("AUTHENTICATED".equals(otrState)) { + text = Chat.this.getString(R.string.chat_otrstate_authenticated); + } else { + text = Chat.this.getString(R.string.chat_otrstate_plaintext); + } + if (mContactOtrState != null) + mContactOtrState.setText(text); + } + + /** + * Update the contact status icon. + */ + private void updateContactStatusIcon() { + if (mCompact) + return; + String id = mContact.getAvatarId(); + if (id == null) + id = ""; + Log.d(TAG, "update contact icon : " + id); + if (!id.equals(mCurrentAvatarId)) { + Drawable avatar = getAvatarDrawable(mContact.getAvatarId()); + mAvatarStatusDrawable.setDrawableByLayerId(R.id.avatar, avatar); + mCurrentAvatarId = id; + } + mContactStatusIcon.setImageLevel(mContact.getStatus()); + } + + /** + * Get a Drawable containing the avatar icon. + * @param avatarId the avatar id to retrieve or null to get default + * @return a Drawable + */ + private Drawable getAvatarDrawable(String avatarId) { + Drawable avatarDrawable = null; + if (avatarId != null) { + Uri uri = AvatarProvider.CONTENT_URI.buildUpon().appendPath(avatarId).build(); + InputStream in = null; + try { + try { + in = getContentResolver().openInputStream(uri); + avatarDrawable = Drawable.createFromStream(in, avatarId); + } finally { + if (in != null) + in.close(); + } + } catch (IOException e) { + Log.w(TAG, "Error while setting the avatar", e); + } + } + if (avatarDrawable == null) + avatarDrawable = getResources().getDrawable(R.drawable.beem_launcher_icon_silver); + return avatarDrawable; + } + + /** + * Prepare the status icons map. + */ + private void prepareIconsStatus() { + mStatusIconsMap.put(Status.CONTACT_STATUS_AVAILABLE, + BitmapFactory.decodeResource(getResources(), android.R.drawable.presence_online)); + mStatusIconsMap.put(Status.CONTACT_STATUS_AVAILABLE_FOR_CHAT, + BitmapFactory.decodeResource(getResources(), android.R.drawable.presence_online)); + mStatusIconsMap.put(Status.CONTACT_STATUS_AWAY, + BitmapFactory.decodeResource(getResources(), android.R.drawable.presence_away)); + mStatusIconsMap.put(Status.CONTACT_STATUS_BUSY, + BitmapFactory.decodeResource(getResources(), android.R.drawable.presence_busy)); + mStatusIconsMap.put(Status.CONTACT_STATUS_DISCONNECT, + BitmapFactory.decodeResource(getResources(), android.R.drawable.presence_offline)); + mStatusIconsMap.put(Status.CONTACT_STATUS_UNAVAILABLE, + BitmapFactory.decodeResource(getResources(), R.drawable.status_requested)); + } + + /** + * Add smileys Spannable to a message string. + * + * @param msg the message containing optional smileys strings + * @return the message with smileys spannable + */ + private CharSequence addSmileysToMessage(String msg) { + SmileyParser parser = SmileyParser.getInstance(); + return parser.addSmileySpans(msg); + } + + /** + * {@inheritDoc}. + */ + private final class BeemServiceConnection implements ServiceConnection { + + /** + * Constructor. + */ + public BeemServiceConnection() { + } + + /** + * {@inheritDoc}. + */ + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mXmppFacade = IXmppFacade.Stub.asInterface(service); + try { + mRoster = mXmppFacade.getRoster(); + if (mRoster != null) + mRoster.addRosterListener(mBeemRosterListener); + mChatManager = mXmppFacade.getChatManager(); + if (mChatManager != null) { + mChatManager.addChatCreationListener(mChatManagerListener); + changeCurrentChat(mContact); + } + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + } + + /** + * {@inheritDoc}. + */ + @Override + public void onServiceDisconnected(ComponentName name) { + mXmppFacade = null; + try { + mRoster.removeRosterListener(mBeemRosterListener); + mChatManager.removeChatCreationListener(mChatManagerListener); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + } + } + + /** + * {@inheritDoc}. + */ + private class BeemRosterListener extends IBeemRosterListener.Stub { + + /** + * Constructor. + */ + public BeemRosterListener() { + } + + /** + * {@inheritDoc}. + */ + @Override + public void onEntriesAdded(List addresses) throws RemoteException { + } + + /** + * {@inheritDoc}. + */ + @Override + public void onEntriesDeleted(List addresses) throws RemoteException { + } + + /** + * {@inheritDoc}. + */ + @Override + public void onEntriesUpdated(List addresses) throws RemoteException { + } + + /** + * {@inheritDoc}. + */ + @Override + public void onPresenceChanged(final PresenceAdapter presence) throws RemoteException { + if (mContact.getJID().equals(StringUtils.parseBareAddress(presence.getFrom()))) { + mHandler.post(new Runnable() { + @Override + public void run() { + mContact.setStatus(presence.getStatus()); + mContact.setMsgState(presence.getStatusText()); + updateContactInformations(); + updateContactStatusIcon(); + } + }); + } + } + } + + /** + * {@inheritDoc}. + */ + private class OnMessageListener extends IMessageListener.Stub { + + /** + * Constructor. + */ + public OnMessageListener() { + } + + /** + * {@inheritDoc}. + */ + @Override + public void processMessage(IChat chat, final Message msg) throws RemoteException { + final String fromBareJid = StringUtils.parseBareAddress(msg.getFrom()); + + if (mContact.getJID().equals(fromBareJid)) { + mHandler.post(new Runnable() { + + @Override + public void run() { + if (msg.getType() == Message.MSG_TYPE_ERROR) { + mListMessages.add(new MessageText(fromBareJid, mContact.getName(), msg.getBody(), true, msg + .getTimestamp())); + mMessagesListAdapter.notifyDataSetChanged(); + } else if (msg.getBody() != null) { + MessageText lastMessage = null; + if (mListMessages.size() != 0) + lastMessage = mListMessages.get(mListMessages.size() - 1); + + if (lastMessage != null && lastMessage.getBareJid().equals(fromBareJid)) { + lastMessage.setMessage(lastMessage.getMessage().concat("\n" + msg.getBody())); + lastMessage.setTimestamp(msg.getTimestamp()); + mListMessages.set(mListMessages.size() - 1, lastMessage); + } else if (msg.getBody() != null) + mListMessages.add(new MessageText(fromBareJid, mContact.getName(), msg.getBody(), + false, msg.getTimestamp())); + mMessagesListAdapter.notifyDataSetChanged(); + } + } + }); + } + } + + /** + * {@inheritDoc}. + */ + @Override + public void stateChanged(IChat chat) throws RemoteException { + final String state = chat.getState(); + mHandler.post(new Runnable() { + @Override + public void run() { + String text = null; + if ("active".equals(state)) { + text = Chat.this.getString(R.string.chat_state_active); + } else if ("composing".equals(state)) { + text = Chat.this.getString(R.string.chat_state_composing); + } else if ("gone".equals(state)) { + text = Chat.this.getString(R.string.chat_state_gone); + } else if ("inactive".equals(state)) { + text = Chat.this.getString(R.string.chat_state_inactive); + } else if ("paused".equals(state)) { + text = Chat.this.getString(R.string.chat_state_active); + } + if (!mCompact) + mContactChatState.setText(text); + } + }); + + } + + @Override + public void otrStateChanged(final String otrState) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + updateOtrInformations(otrState); + mListMessages.add(new MessageText("", "", otrState, false)); + mMessagesListAdapter.notifyDataSetChanged(); + } + }); + + } + } + + /** + * {@inheritDoc}. + */ + private class MessagesListAdapter extends BaseAdapter { + + /** + * Constructor. + */ + public MessagesListAdapter() { + } + + /** + * Returns the number of messages contained in the messages list. + * @return The number of messages contained in the messages list. + */ + @Override + public int getCount() { + return mListMessages.size(); + } + + /** + * Return an item from the messages list that is positioned at the position passed by parameter. + * @param position The position of the requested item. + * @return The item from the messages list at the requested position. + */ + @Override + public Object getItem(int position) { + return mListMessages.get(position); + } + + /** + * Return the id of an item from the messages list that is positioned at the position passed by parameter. + * @param position The position of the requested item. + * @return The id of an item from the messages list at the requested position. + */ + @Override + public long getItemId(int position) { + return position; + } + + /** + * Return the view of an item from the messages list. + * @param position The position of the requested item. + * @param convertView The old view to reuse if possible. + * @param parent The parent that this view will eventually be attached to. + * @return A View corresponding to the data at the specified position. + */ + public View getView(int position, View convertView, ViewGroup parent) { + View sv; + if (convertView == null) { + LayoutInflater inflater = Chat.this.getLayoutInflater(); + sv = inflater.inflate(R.layout.chat_msg_row, null); + } else { + sv = convertView; + } + DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM); + MessageText msg = mListMessages.get(position); + TextView msgName = (TextView) sv.findViewById(R.id.chatmessagename); + msgName.setText(msg.getName()); + msgName.setTextColor(Color.WHITE); + msgName.setError(null); + TextView msgText = (TextView) sv.findViewById(R.id.chatmessagetext); + CharSequence msgBody = addSmileysToMessage(msg.getMessage()); + msgText.setText(msgBody); + registerForContextMenu(msgText); + TextView msgDate = (TextView) sv.findViewById(R.id.chatmessagedate); + if (msg.getTimestamp() != null) { + String date = df.format(msg.getTimestamp()); + msgDate.setText(date); + } + if (msg.isError()) { + String err = getString(R.string.chat_error); + msgName.setText(err); + msgName.setTextColor(Color.RED); + msgName.setError(err); + } + return sv; + } + } + + /** + * Class which simplify an Xmpp text message. + * @author Jean-Manuel Da Silva + */ + private class MessageText { + private String mBareJid; + private String mName; + private String mMessage; + private boolean mIsError; + private Date mTimestamp; + + /** + * Constructor. + * @param bareJid A String containing the bare JID of the message's author. + * @param name A String containing the name of the message's author. + * @param message A String containing the message. + */ + public MessageText(final String bareJid, final String name, final String message) { + mBareJid = bareJid; + mName = name; + mMessage = message; + mIsError = false; + } + + /** + * Constructor. + * @param bareJid A String containing the bare JID of the message's author. + * @param name A String containing the name of the message's author. + * @param message A String containing the message. + * @param isError if the message is an error message. + */ + public MessageText(final String bareJid, final String name, final String message, final boolean isError) { + mBareJid = bareJid; + mName = name; + mMessage = message; + mIsError = isError; + } + + /** + * Constructor. + * @param bareJid A String containing the bare JID of the message's author. + * @param name A String containing the name of the message's author. + * @param message A String containing the message. + * @param isError if the message is an error message. + * @param date the time of the message. + */ + public MessageText(final String bareJid, final String name, final String message, final boolean isError, + final Date date) { + mBareJid = bareJid; + mName = name; + mMessage = message; + mIsError = isError; + mTimestamp = date; + } + + /** + * JID attribute accessor. + * @return A String containing the bare JID of the message's author. + */ + public String getBareJid() { + return mBareJid; + } + + /** + * Name attribute accessor. + * @return A String containing the name of the message's author. + */ + public String getName() { + return mName; + } + + /** + * Message attribute accessor. + * @return A String containing the message. + */ + public String getMessage() { + return mMessage; + } + + /** + * JID attribute mutator. + * @param bareJid A String containing the author's bare JID of the message. + */ + @SuppressWarnings("unused") + public void setBareJid(String bareJid) { + mBareJid = bareJid; + } + + /** + * Name attribute mutator. + * @param name A String containing the author's name of the message. + */ + @SuppressWarnings("unused") + public void setName(String name) { + mName = name; + } + + /** + * Message attribute mutator. + * @param message A String containing a message. + */ + public void setMessage(String message) { + mMessage = message; + } + + /** + * Get the message type. + * @return true if the message is an error message. + */ + public boolean isError() { + return mIsError; + } + + /** + * Set the Date of the message. + * @param date date of the message. + */ + public void setTimestamp(Date date) { + mTimestamp = date; + } + + /** + * Get the Date of the message. + * @return if it is a delayed message get the date the message was sended. + */ + public Date getTimestamp() { + return mTimestamp; + } + + } + + /** + * This class is in charge of getting the new chat in the activity if someone talk to you. + */ + private class ChatManagerListener extends IChatManagerListener.Stub { + + /** + * Constructor. + */ + public ChatManagerListener() { + } + + @Override + public void chatCreated(IChat chat, boolean locally) { + if (locally) + return; + try { + String contactJid = mContact.getJIDWithRes(); + String chatJid = chat.getParticipant().getJIDWithRes(); + if (chatJid.equals(contactJid)) { + // This should not be happened but to be sure + if (mChat != null) { + mChat.setOpen(false); + mChat.removeMessageListener(mMessageListener); + } + mChat = chat; + mChat.setOpen(true); + mChat.addMessageListener(mMessageListener); + mChatManager.deleteChatNotification(mChat); + } + } catch (RemoteException ex) { + Log.e(TAG, "A remote exception occurs during the creation of a chat", ex); + } + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/ContactList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/ContactList.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,590 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + +*/ +package com.beem.project.beem.ui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import android.app.Dialog; +import android.content.ComponentName; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.PagerTabStrip; +import android.support.v4.view.ViewPager; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; + +import com.beem.project.beem.BeemApplication; +import com.beem.project.beem.R; +import com.beem.project.beem.service.Contact; +import com.beem.project.beem.service.PresenceAdapter; +import com.beem.project.beem.service.aidl.IBeemRosterListener; +import com.beem.project.beem.service.aidl.IChatManager; +import com.beem.project.beem.service.aidl.IRoster; +import com.beem.project.beem.service.aidl.IXmppFacade; +import com.beem.project.beem.ui.dialogs.builders.Alias; +import com.beem.project.beem.ui.dialogs.builders.ChatList; +import com.beem.project.beem.ui.dialogs.builders.DeleteContact; +import com.beem.project.beem.ui.dialogs.builders.ResendSubscription; +import com.beem.project.beem.utils.BeemBroadcastReceiver; + +import org.jivesoftware.smack.util.StringUtils; + +import static com.google.android.apps.iosched.util.LogUtils.*; + +/** + * The contact list activity displays the roster of the user. + */ +public class ContactList extends FragmentActivity { + + private static final Intent SERVICE_INTENT = new Intent(); + static { + SERVICE_INTENT.setComponent(new ComponentName("com.beem.project.beem", "com.beem.project.beem.BeemService")); + } + + private static final String TAG = makeLogTag(ContactList.class); + private static final float PAGER_TAB_SECONDARY_ALPHA = 0.5f; + private final List mListGroup = new ArrayList(); + + /** Map containing a list of the different contacts of a given group. + * Each list is a @{link SortedList} so there is no need to sort it again. + * */ + private final Map> mContactOnGroup = new HashMap>(); + private final ServiceConnection mServConn = new BeemServiceConnection(); + private final BeemBroadcastReceiver mReceiver = new BeemBroadcastReceiver(); + private final Map contactListAdapters = new HashMap(); + + private final BeemRosterListener mBeemRosterListener = new BeemRosterListener(); + private IRoster mRoster; + private IXmppFacade mXmppFacade; + private IChatManager mChatManager; + private SharedPreferences mSettings; + private boolean mBinded; + private ViewPager viewPager; + private ListPagerAdapter groupsPagesAdapter; + private PagerTabStrip pagerTabs; + + /** + * Constructor. + */ + public ContactList() { + } + + /** + * Callback for menu creation. + * @param menu the menu created + * @return true on success, false otherwise + */ + @Override + public final boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.contact_list, menu); + return true; + } + + @Override + public final boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.contact_list_menu_settings: + startActivity(new Intent(this, Settings.class)); + return true; + case R.id.contact_list_menu_add_contact: + startActivity(new Intent(ContactList.this, AddContact.class)); + return true; + case R.id.menu_change_status: + startActivity(new Intent(ContactList.this, ChangeStatus.class)); + return true; + case R.id.contact_list_menu_chatlist: + List openedChats; + try { + openedChats = mChatManager.getOpenedChatList(); + LOGD(TAG, "opened chats = " + openedChats); + Dialog chatList = new ChatList(ContactList.this, openedChats).create(); + chatList.show(); + } catch (RemoteException e) { + e.printStackTrace(); + } + return true; + case R.id.menu_disconnect: + stopService(SERVICE_INTENT); + finish(); + return true; + default: + return false; + } + } + + + @Override + protected void onCreate(Bundle saveBundle) { + super.onCreate(saveBundle); + mSettings = PreferenceManager.getDefaultSharedPreferences(this); + setContentView(R.layout.contactlist); + + this.registerReceiver(mReceiver, new IntentFilter(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED)); + + viewPager = (ViewPager) findViewById(R.id.pager); + groupsPagesAdapter = new ListPagerAdapter(getSupportFragmentManager(), viewPager); + pagerTabs = (PagerTabStrip) findViewById(R.id.tabstrip); + pagerTabs.setTabIndicatorColorResource(R.color.vert_manu); + pagerTabs.setNonPrimaryAlpha(PAGER_TAB_SECONDARY_ALPHA); + + mListGroup.add(getString(R.string.contact_list_all_contact)); + mListGroup.add(getString(R.string.contact_list_no_group)); + groupsPagesAdapter.notifyDataSetChanged(); + } + + @Override + protected void onStart() { + super.onStart(); + if (!mSettings.getBoolean(BeemApplication.HIDE_GROUPS_KEY, false)) + showGroups(); + else + hideGroups(); + + if (!mBinded) + mBinded = bindService(SERVICE_INTENT, mServConn, BIND_AUTO_CREATE); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPause() { + super.onPause(); + try { + if (mRoster != null) { + mRoster.removeRosterListener(mBeemRosterListener); + mRoster = null; + } + } catch (RemoteException e) { + LOGD("ContactList", "Remote exception", e); + } + if (mBinded) { + unbindService(mServConn); + mBinded = false; + } + mXmppFacade = null; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + this.unregisterReceiver(mReceiver); + LOGV(TAG, "onDestroy activity"); + } + + /** + * Get a {@link ContactListAdapter} for a group. + * The {@link ContactListAdapter} will be created if it is not exist. + * @param group the group + * @return the adapter + */ + ContactListAdapter getContactListAdapter(String group) { + synchronized (contactListAdapters) { + ContactListAdapter contactListAdapter = contactListAdapters.get(group); + if (contactListAdapter == null) { + contactListAdapter = new ContactListAdapter(ContactList.this); + contactListAdapters.put(group, contactListAdapter); + } + boolean hideDisconnected = mSettings.getBoolean(BeemApplication.SHOW_OFFLINE_CONTACTS_KEY, false); + contactListAdapter.setOnlineOnly(hideDisconnected); + return contactListAdapter; + } + } + + /** + * Exectute a context menu action on a specified contact. + * @param itemId the id of the menu action + * @param contact the contact + */ + void doContextMenuAction(int itemId, Contact contact) { + switch (itemId) { + case R.id.contact_list_context_menu_call_item: + try { + mXmppFacade.call(contact.getJID() + "/psi"); + } catch (RemoteException e) { + e.printStackTrace(); + } + break; + case R.id.contact_list_context_menu_userinfo_alias: + Dialog alias = new Alias(ContactList.this, mRoster, contact).create(); + alias.show(); + break; + case R.id.contact_list_context_menu_userinfo_subscription: + Dialog subscription = new ResendSubscription(ContactList.this, + mXmppFacade, contact).create(); + subscription.show(); + break; + case R.id.contact_list_context_menu_userinfo_delete: + Dialog delete = new DeleteContact(ContactList.this, mRoster, contact).create(); + delete.show(); + break; + default: + LOGW(TAG, "Context menu action not supported" + itemId); + break; + } + + } + + /** + * Show the groups view. + */ + private void showGroups() { + pagerTabs.setVisibility(View.VISIBLE); + } + + /** + * Hide the groups view. + */ + private void hideGroups() { + pagerTabs.setVisibility(View.GONE); + } + + /** + * Remove old groups on the banner. + * @throws RemoteException if an error occur when communicating with the service + */ + private void cleanBannerGroup() throws RemoteException { + if (mListGroup.size() <= 2) + return; + List rosterGroups = mRoster.getGroupsNames(); + Collections.sort(rosterGroups); + List realGroups = mListGroup.subList(1, mListGroup.size() - 1); + realGroups.clear(); + realGroups.addAll(rosterGroups); + groupsPagesAdapter.notifyDataSetChanged(); + } + + /** + * Add a contact to the special list No Group and All contacts. + * The contact will be added if the list is not the current list otherwise + * the list must be modified in a Handler. + * + * @param contact the contact to add. + */ + private void addToSpecialList(Contact contact) { + List groups = contact.getGroups(); + + ContactListAdapter adapter = getContactListAdapter(getString(R.string.contact_list_all_contact)); + adapter.put(contact); + if (groups.isEmpty()) { + adapter = getContactListAdapter(getString(R.string.contact_list_no_group)); + adapter.put(contact); + } + } + + /** + * Add a new group in the GroupBanner. + * + * @param group the group to add + */ + private void addGroupInBanner(String group) { + List realGroups = mListGroup.subList(1, mListGroup.size() - 1); + if (!mListGroup.contains(group)) { + boolean added = false; + // insert group in sorted list + for (ListIterator iterator = realGroups.listIterator(); iterator.hasNext();) { + String currentGroup = (String) iterator.next(); + if (currentGroup.compareTo(group) > 0) { + iterator.previous(); + iterator.add(group); + added = true; + break; + } + } + if (!added) + realGroups.add(group); + groupsPagesAdapter.notifyDataSetChanged(); + } + } + + /** + * Listener on service event. + */ + private class BeemRosterListener extends IBeemRosterListener.Stub { + /** + * Constructor. + */ + public BeemRosterListener() { + } + + /** + * {@inheritDoc} + * Simple stategy to handle the onEntriesAdded event. + * if contact has to be shown : + *
    + *
  • add him to his groups
  • + *
  • add him to the specials groups + *
+ */ + @Override + public void onEntriesAdded(final List addresses) throws RemoteException { + for (String newName : addresses) { + final Contact contact = mRoster.getContact(StringUtils.parseBareAddress(newName)); + putContactInList(contact); + } + } + + /** + * {@inheritDoc} + * Simple stategy to handle the onEntriesDeleted event. + *
    + *
  • Remove the contact from all groups
  • + *
+ */ + @Override + public void onEntriesDeleted(final List addresses) throws RemoteException { + LOGD(TAG, "onEntries deleted " + addresses); + for (String cToDelete : addresses) { + final Contact contact = new Contact(cToDelete); + for (final ContactListAdapter adapter : contactListAdapters.values()) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + adapter.remove(contact); + } + }); + } + } + cleanBannerGroup(); + + } + + /** + * {@inheritDoc} + * Simple stategy to handle the onEntriesUpdated event. + *
    + *
  • Remove the contact from all groups
  • + *
  • if contact has to be shown add it to his groups
  • + *
  • if contact has to be shown add it to the specials groups
  • + *
+ */ + @Override + public void onEntriesUpdated(final List addresses) throws RemoteException { + LOGD(TAG, "onEntries updated " + addresses); + for (String cToDelete : addresses) { + Contact contact = new Contact(cToDelete); + for (ContactListAdapter adapter : contactListAdapters.values()) { + adapter.remove(contact); + } + } + for (String newName : addresses) { + final Contact contact = mRoster.getContact(StringUtils.parseBareAddress(newName)); + putContactInList(contact); + } + cleanBannerGroup(); + } + + /** + * {@inheritDoc} + * Simple stategy to handle the onPresenceChanged event. + *
    + *
  • Remove the contact from all groups
  • + *
  • if contact has to be shown add it to his groups
  • + *
  • if contact has to be shown add it to the specials groups
  • + *
+ */ + @Override + public void onPresenceChanged(PresenceAdapter presence) throws RemoteException { + String from = presence.getFrom(); + final Contact contact = mRoster.getContact(StringUtils.parseBareAddress(from)); + putContactInList(contact); + } + + /** + * Put a contact in the different group list. + * @param contact the contact + */ + private void putContactInList(final Contact contact) { + List groups = contact.getGroups(); + for (final String group : groups) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + addGroupInBanner(group); + ContactListAdapter contactListAdapter = getContactListAdapter(group); + contactListAdapter.put(contact); + } + }); + } + + runOnUiThread(new Runnable() { + + @Override + public void run() { + addToSpecialList(contact); + } + }); + } + } + + /** + * The service connection used to connect to the Beem service. + */ + private class BeemServiceConnection implements ServiceConnection { + + /** + * Constructor. + */ + public BeemServiceConnection() { + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mXmppFacade = IXmppFacade.Stub.asInterface(service); + try { + mRoster = mXmppFacade.getRoster(); + if (mRoster != null) { + List tmpGroupList = mRoster.getGroupsNames(); + cleanBannerGroup(); + synchronized (contactListAdapters) { + for (ContactListAdapter ca : contactListAdapters.values()) { + ca.clear(); + } + } + assignContactToGroups(mRoster.getContactList(), tmpGroupList); + + mRoster.addRosterListener(mBeemRosterListener); + LOGD(TAG, "add roster listener"); + mChatManager = mXmppFacade.getChatManager(); + } + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + try { + mRoster.removeRosterListener(mBeemRosterListener); + } catch (RemoteException e) { + e.printStackTrace(); + } + mXmppFacade = null; + mChatManager = null; + mRoster = null; + mListGroup.clear(); + mContactOnGroup.clear(); + mBinded = false; + + } + + /** + * Assign the differents contact to their groups. + * This methods will fill the mContactOnGroup map. + * + * @param contacts list of contacts + * @param groupNames list of existing groups + */ + private void assignContactToGroups(List contacts, List groupNames) { + for (Contact c : contacts) { + addToSpecialList(c); + + List groups = c.getGroups(); + + for (String currentGroup : groups) { + addGroupInBanner(currentGroup); + ContactListAdapter cl = getContactListAdapter(currentGroup); + cl.put(c); + } + } + } + + } + + /** + * PagerAdapter for the contact list. + */ + private class ListPagerAdapter extends FragmentPagerAdapter { + + /** + * Create a {@link ListPagerAdapter}. + * @param fm the {@link FragmentManager} + * @param viewPager the {@link ViewPager} associate with this adapter + */ + public ListPagerAdapter(final FragmentManager fm, final ViewPager viewPager) { + super(fm); + viewPager.setAdapter(this); + } + + @Override + public Fragment getItem(int position) { + String group = mListGroup.get(position); + ContactListFragment f = ContactListFragment.newInstance(group); + f.setListAdapter(getContactListAdapter(group)); + return f; + } + + @Override + public int getCount() { + return mListGroup.size(); + } + + @Override + public String getPageTitle(int position) { + return mListGroup.get(position); + } + + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/ContactListAdapter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/ContactListAdapter.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,294 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2011 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ +package com.beem.project.beem.ui; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.net.Uri; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ImageView; +import android.widget.TextView; + +import com.beem.project.beem.BeemApplication; +import com.beem.project.beem.R; +import com.beem.project.beem.providers.AvatarProvider; +import com.beem.project.beem.service.Contact; +import com.beem.project.beem.utils.SortedList; +import com.beem.project.beem.utils.Status; + +/** + * An Adapter for the contact list. + * It displays a list of contact in a particular group. + * + */ +public class ContactListAdapter extends BaseAdapter implements Filterable { + private static final String TAG = ContactListAdapter.class.getSimpleName(); + private final ComparatorContactListByStatusAndName mComparator = + new ComparatorContactListByStatusAndName(); + private List mCurrentList; + private final List allContacts = new SortedList(new LinkedList(), mComparator); + private final List onlineContacts = new SortedList(new LinkedList(), mComparator); + private final Filter mFilter = new ContactFilter(); + private final Context context; + private LayoutInflater mInflater; + + private boolean showOnlineOnly; + + /** + * Create a ContactListAdapter. + * @param c the android context + */ + public ContactListAdapter(final Context c) { + mCurrentList = allContacts; + context = c; + mInflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + @Override + public int getCount() { + return mCurrentList.size(); + } + + @Override + public Object getItem(int position) { + return mCurrentList.get(position); + } + + @Override + public long getItemId(int position) { + return mCurrentList.get(position).hashCode(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = convertView; + if (convertView == null) { + v = mInflater.inflate(R.layout.contactlistcontact, null); + } + Contact c = mCurrentList.get(position); + bindView(v, c); + return v; + } + + /** + * Put a contact in the list. + * @param c the contact + */ + public void put(Contact c) { + put(c, allContacts); + if (Status.statusOnline(c.getStatus())) + put(c, onlineContacts); + notifyDataSetChanged(); + } + + /** + * Remove a contact from the list. + * + * @param c the contact + */ + public void remove(Contact c) { + allContacts.remove(c); + onlineContacts.remove(c); + notifyDataSetChanged(); + } + + /** + * Clear the contact list. + */ + public void clear() { + allContacts.clear(); + onlineContacts.clear(); + notifyDataSetChanged(); + } + + @Override + public Filter getFilter() { + return mFilter; + } + + /** + * Bind a contact to the view. + * @param view the row view. + * @param curContact the contact. + */ + private void bindView(View view, Contact curContact) { + if (curContact != null) { + TextView v = (TextView) view.findViewById(R.id.contactlistpseudo); + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context); + if (settings.getBoolean(BeemApplication.SHOW_JID, false)) + v.setText(curContact.getJID()); + else + v.setText(curContact.getName()); + v = (TextView) view.findViewById(R.id.contactlistmsgperso); + v.setText(curContact.getMsgState()); + ImageView img = (ImageView) view.findViewById(R.id.avatar); + String avatarId = curContact.getAvatarId(); + int contactStatus = curContact.getStatus(); + Drawable avatar = getAvatarStatusDrawable(avatarId); + img.setImageDrawable(avatar); + img.setImageLevel(contactStatus); + } + } + + /** + * Get a LayerDrawable containing the avatar and the status icon. + * The status icon will change with the level of the drawable. + * @param avatarId the avatar id to retrieve or null to get default + * @return a LayerDrawable + */ + private Drawable getAvatarStatusDrawable(String avatarId) { + Drawable avatarDrawable = null; + if (avatarId != null) { + Uri uri = AvatarProvider.CONTENT_URI.buildUpon().appendPath(avatarId).build(); + InputStream in = null; + try { + try { + in = context.getContentResolver().openInputStream(uri); + avatarDrawable = Drawable.createFromStream(in, avatarId); + } finally { + if (in != null) + in.close(); + } + } catch (IOException e) { + Log.w(TAG, "Error while setting the avatar " + avatarId, e); + } + } + if (avatarDrawable == null) + avatarDrawable = context.getResources().getDrawable(R.drawable.beem_launcher_icon_silver); + LayerDrawable ld = (LayerDrawable) context.getResources().getDrawable(R.drawable.avatar_status); + ld.setLayerInset(1, 36, 36, 0, 0); + ld.setDrawableByLayerId(R.id.avatar, avatarDrawable); + return ld; + } + + /** + * Put a contact in a list. + * Helper method. + * + * @param c the contact + * @param list the list + */ + private void put(Contact c, List list) { + list.remove(c); + list.add(c); + } + + /** + * Tell if the list display only online contacts. + * + * @return true if only online contacts are shown + */ + public boolean isOnlineOnly() { + return showOnlineOnly; + } + + /** + * Set the list to display only the online contacts. + * + * @param online true to display only online contacts + */ + public void setOnlineOnly(boolean online) { + if (online != showOnlineOnly) { + showOnlineOnly = online; + mCurrentList = showOnlineOnly ? onlineContacts : allContacts; + notifyDataSetChanged(); + } + } + + /** + * A Filter which select Contact to display by searching in ther Jid. + */ + private class ContactFilter extends Filter { + + /** + * Create a ContactFilter. + */ + public ContactFilter() { } + + @Override + protected Filter.FilterResults performFiltering(CharSequence constraint) { + Log.d(TAG, "performFiltering"); + List result = mCurrentList; + if (constraint.length() > 0) { + result = new LinkedList(); + for (Contact c : mCurrentList) { + if (c.getJID().contains(constraint)) + result.add(c); + } + } + Filter.FilterResults fr = new Filter.FilterResults(); + fr.values = result; + fr.count = result.size(); + return fr; + } + + @Override + protected void publishResults(CharSequence constraint, Filter.FilterResults results) { + Log.d(TAG, "publishResults"); + List contacts = (List) results.values; + mCurrentList = contacts; + notifyDataSetChanged(); + } + } + + /** + * Comparator Contact by status and name. + */ + private static class ComparatorContactListByStatusAndName implements Comparator { + /** + * Constructor. + */ + public ComparatorContactListByStatusAndName() { + } + + @Override + public int compare(T c1, T c2) { + if (((Contact) c1).getStatus() < ((Contact) c2).getStatus()) { + return 1; + } else if (((Contact) c1).getStatus() > ((Contact) c2).getStatus()) { + return -1; + } else + return ((Contact) c1).getName().compareToIgnoreCase(((Contact) c2).getName()); + } + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/ContactListFragment.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/ContactListFragment.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,172 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2011 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ +package com.beem.project.beem.ui; + +import java.util.List; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.ListFragment; +import android.view.ContextMenu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.ListAdapter; +import android.widget.ListView; + +import com.beem.project.beem.R; +import com.beem.project.beem.service.Contact; + +/** + * A Fragment which display a list of contacts. + */ +public class ContactListFragment extends ListFragment { + private String group; + private ContactList hostActivity; + private Contact mSelectedContact; + + /** + * Create a ContactListFragment. + * @param group the group name + * @return the ContactListFragment + */ + public static ContactListFragment newInstance(String group) { + ContactListFragment f = new ContactListFragment(); + Bundle b = new Bundle(); + b.putString("group", group); + f.setArguments(b); + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + parseArguments(); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + hostActivity = (ContactList) activity; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + ListAdapter adapter = hostActivity.getContactListAdapter(group); + setListAdapter(adapter); + registerForContextMenu(getListView()); + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + ContactListAdapter a = (ContactListAdapter) getListAdapter(); + Contact c = (Contact) a.getItem(position); + Intent i = new Intent(getActivity(), Chat.class); + i.setData(c.toUri()); + startActivity(i); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + MenuInflater inflater = hostActivity.getMenuInflater(); + inflater.inflate(R.menu.contactlist_context, menu); + AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + mSelectedContact = (Contact) getListAdapter().getItem(info.position); + menu.setHeaderTitle(mSelectedContact.getJID()); + } + + /** + * Parse the arguments submit to the Fragment. + */ + private void parseArguments() { + Bundle b = getArguments(); + if (b == null) + return; + group = b.getString("group"); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + Intent in; + boolean result = false; + if (mSelectedContact != null) { + switch (item.getItemId()) { + case R.id.contact_list_context_menu_chat_item: + List res = mSelectedContact.getMRes(); + if (res.isEmpty()) { + break; + } + for (String resv : res) { + in = new Intent(hostActivity, Chat.class); + in.setData(mSelectedContact.toUri(resv)); + item.getSubMenu().add(resv).setIntent(in); + } + result = true; + break; + case R.id.contact_list_context_menu_call_item: + hostActivity.doContextMenuAction(item.getItemId(), mSelectedContact); + break; + case R.id.contact_list_context_menu_user_info: + item.getSubMenu().setHeaderTitle(mSelectedContact.getJID()); + result = true; + break; + case R.id.contact_list_context_menu_userinfo_alias: + hostActivity.doContextMenuAction(item.getItemId(), mSelectedContact); + result = true; + break; + case R.id.contact_list_context_menu_userinfo_group: + in = new Intent(hostActivity, GroupList.class); + in.putExtra("contact", mSelectedContact); + startActivity(in); + result = true; + break; + case R.id.contact_list_context_menu_userinfo_subscription: + hostActivity.doContextMenuAction(item.getItemId(), mSelectedContact); + result = true; + break; + case R.id.contact_list_context_menu_userinfo_block: + result = true; + break; + case R.id.contact_list_context_menu_userinfo_delete: + hostActivity.doContextMenuAction(item.getItemId(), mSelectedContact); + result = true; + break; + default: + result = super.onContextItemSelected(item); + break; + } + return result; + } + return super.onContextItemSelected(item); + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/GroupList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/GroupList.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,263 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui; + +import java.util.ArrayList; +import java.util.List; + +import android.app.ListActivity; +import android.content.ComponentName; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnKeyListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.CheckedTextView; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.AdapterView.OnItemClickListener; + +import com.beem.project.beem.BeemService; +import com.beem.project.beem.R; +import com.beem.project.beem.service.Contact; +import com.beem.project.beem.service.aidl.IRoster; +import com.beem.project.beem.service.aidl.IXmppFacade; +import com.beem.project.beem.utils.BeemBroadcastReceiver; + +/** + * That activity permit to manage user groups. + * @author nikita + */ +public class GroupList extends ListActivity { + + private static final Intent SERVICE_INTENT = new Intent(); + + private final ServiceConnection mServConn = new BeemServiceConnection(); + private final BeemBroadcastReceiver mReceiver = new BeemBroadcastReceiver(); + private IXmppFacade mXmppFacade; + private IRoster mRoster; + private String mJID; + private ArrayAdapter mGroups; + private Contact mContact; + private TextView mText; + private final List mStrings = new ArrayList(); + + static { + SERVICE_INTENT.setComponent(new ComponentName("com.beem.project.beem", "com.beem.project.beem.BeemService")); + } + + /** + * Constructor. + */ + public GroupList() { + } + + /* (non-Javadoc) + * @see android.app.Activity#onCreate(android.os.Bundle) + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.group_list); + mContact = getIntent().getParcelableExtra("contact"); + mJID = mContact.getJID(); + final ListView listView = getListView(); + + listView.setItemsCanFocus(false); + listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + listView.setOnItemClickListener(new GroupOnItemClickListener()); + + mText = (TextView) findViewById(R.id.GroupListText); + mText.setOnKeyListener(new GroupListOnKeyListener()); + this.registerReceiver(mReceiver, new IntentFilter(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED)); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onResume() { + super.onResume(); + bindService(new Intent(this, BeemService.class), mServConn, BIND_AUTO_CREATE); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onPause() { + super.onPause(); + unbindService(mServConn); + } + + /* (non-Javadoc) + * @see android.app.Activity#onDestroy() + */ + @Override + protected void onDestroy() { + super.onDestroy(); + this.unregisterReceiver(mReceiver); + } + + /** + * init activity list adapter. + */ + private void setAdapter() { + try { + for (String group : mRoster.getGroupsNames()) { + mStrings.add(group); + } + mGroups = new ArrayAdapter(this, android.R.layout.simple_list_item_multiple_choice, mStrings); + setListAdapter(mGroups); + mContact = mRoster.getContact(mJID); + for (String group : mContact.getGroups()) { + getListView().setItemChecked(mGroups.getPosition(group), true); + } + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + /** + * Event when group is added. + */ + private class GroupListOnKeyListener implements OnKeyListener { + + /** + * Constructor. + */ + public GroupListOnKeyListener() { + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + boolean result = false; + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + if (mText.getText().length() == 0) + return false; + String groupname = mText.getText().toString(); + mGroups.add(groupname); + mText.setText(null); + result = true; + break; + default: + result = false; + } + } + return result; + } + + } + + /** + * Event click on list group contact. + */ + private class GroupOnItemClickListener implements OnItemClickListener { + + /** + * Constructor. + */ + public GroupOnItemClickListener() { + } + + @Override + public void onItemClick(AdapterView arg0, View v, int arg2, long arg3) { + CheckedTextView textView = (CheckedTextView) v; + if (!textView.isChecked()) { + try { + mRoster.addContactToGroup(textView.getText().toString(), mJID); + } catch (RemoteException e) { + e.printStackTrace(); + } + } else { + try { + mRoster.removeContactFromGroup(textView.getText().toString(), mJID); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + } + + } + + /** + * The ServiceConnection used to connect to the Beem service. + */ + private class BeemServiceConnection implements ServiceConnection { + + /** + * Constructor. + */ + public BeemServiceConnection() { + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mXmppFacade = IXmppFacade.Stub.asInterface(service); + try { + mRoster = mXmppFacade.getRoster(); + setAdapter(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mXmppFacade = null; + mRoster = null; + } + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/Login.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/Login.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,192 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Application; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.TextView; +import android.widget.Toast; + +import com.beem.project.beem.BeemApplication; +import com.beem.project.beem.R; +import com.beem.project.beem.ui.wizard.Account; +import com.beem.project.beem.utils.BeemConnectivity; + +/** + * This class is the main Activity for the Beem project. + * @author Da Risk + */ +public class Login extends Activity { + + private static final int LOGIN_REQUEST_CODE = 1; + private TextView mTextView; + private boolean mIsResult; + private BeemApplication mBeemApplication; + + /** + * Constructor. + */ + public Login() { + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + PreferenceManager.setDefaultValues(this, R.xml.preferences, false); + Application app = getApplication(); + if (app instanceof BeemApplication) { + mBeemApplication = (BeemApplication) app; + if (mBeemApplication.isConnected()) { + startActivity(new Intent(this, ContactList.class)); + finish(); + } else if (!mBeemApplication.isAccountConfigured()) { + startActivity(new Intent(this, Account.class)); + finish(); + } + } + setContentView(R.layout.login); + mTextView = (TextView) findViewById(R.id.log_as_msg); + } + + @Override + protected void onStart() { + super.onStart(); + if (mBeemApplication.isAccountConfigured() && !mIsResult + && BeemConnectivity.isConnected(getApplicationContext())) { + mTextView.setText(""); + Intent i = new Intent(this, LoginAnim.class); + startActivityForResult(i, LOGIN_REQUEST_CODE); + mIsResult = false; + } else { + mTextView.setText(R.string.login_start_msg); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == LOGIN_REQUEST_CODE) { + mIsResult = true; + if (resultCode == Activity.RESULT_OK) { + startActivity(new Intent(this, ContactList.class)); + finish(); + } else if (resultCode == Activity.RESULT_CANCELED) { + if (data != null) { + String tmp = data.getExtras().getString("message"); + Toast.makeText(Login.this, tmp, Toast.LENGTH_SHORT).show(); + mTextView.setText(tmp); + } + } + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.login, menu); + return true; + } + + @Override + public final boolean onOptionsItemSelected(MenuItem item) { + boolean result; + switch (item.getItemId()) { + case R.id.login_menu_settings: + mTextView.setText(""); + startActivity(new Intent(Login.this, Settings.class)); + result = true; + break; + case R.id.login_menu_about: + createAboutDialog(); + result = true; + break; + case R.id.login_menu_login: + if (mBeemApplication.isAccountConfigured()) { + Intent i = new Intent(this, LoginAnim.class); + startActivityForResult(i, LOGIN_REQUEST_CODE); + } + result = true; + break; + default: + result = false; + break; + } + return result; + } + + /** + * Create an about "BEEM" dialog. + */ + private void createAboutDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + String versionname; + try { + PackageManager pm = getPackageManager(); + PackageInfo pi = pm.getPackageInfo("com.beem.project.beem", 0); + versionname = pi.versionName; + } catch (PackageManager.NameNotFoundException e) { + versionname = ""; + } + String title = getString(R.string.login_about_title, versionname); + builder.setTitle(title).setMessage(R.string.login_about_msg).setCancelable(false); + builder.setNeutralButton(R.string.login_about_button, new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int whichButton) { + dialog.cancel(); + } + }); + AlertDialog aboutDialog = builder.create(); + aboutDialog.show(); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/LoginAnim.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/LoginAnim.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,269 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2011 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.ComponentName; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.IBinder; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import com.beem.project.beem.R; +import com.beem.project.beem.service.LoginAsyncTask; +import com.beem.project.beem.service.aidl.IXmppFacade; + +import de.duenndns.ssl.MemorizingTrustManager; + +/** + * This class is an activity which display an animation during the connection with the server. + * @author Da Risk + */ +public class LoginAnim extends Activity { + + private static final String TAG = "LoginAnim"; + private static final Intent SERVICE_INTENT = new Intent(); + private static final int RECEIVER_PRIORITY = 50; + static { + SERVICE_INTENT.setComponent(new ComponentName("com.beem.project.beem", "com.beem.project.beem.BeemService")); + } + private ImageView mLogo; + private Animation mRotateAnim; + private final ServiceConnection mServConn = new LoginServiceConnection(); + private IXmppFacade mXmppFacade; + private AsyncTask mTask; + private Button mCancelBt; + private TextView mLoginState; + private boolean mBinded; + private BroadcastReceiver mSslReceiver; + + /** + * Constructor. + */ + public LoginAnim() { + } + + /* (non-Javadoc) + * @see android.app.Activity#onCreate(android.os.Bundle) + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.login_anim); + mLoginState = (TextView) findViewById(R.id.loginanim_status_text); + mLogo = (ImageView) findViewById(R.id.loginanim_logo_anim); + mRotateAnim = AnimationUtils.loadAnimation(this, R.anim.rotate_and_scale); + mCancelBt = (Button) findViewById(R.id.loginanim_cancel_button); + mCancelBt.setOnClickListener(new ClickListener()); + mSslReceiver = new BroadcastReceiver() { + public void onReceive(Context ctx, Intent i) { + try { + Log.i(TAG, "Interception the SSL notification"); + PendingIntent pi = i.getParcelableExtra(MemorizingTrustManager.INTERCEPT_DECISION_INTENT_LAUNCH); + pi.send(); + abortBroadcast(); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Error while displaying the SSL dialog", e); + } + } + }; + } + + /* (non-Javadoc) + * @see android.app.Activity#onStart() + */ + @Override + protected void onStart() { + super.onStart(); + mLogo.startAnimation(mRotateAnim); + if (mTask == null) + mTask = new LoginTask(); + if (!mBinded) + mBinded = bindService(LoginAnim.SERVICE_INTENT, mServConn, BIND_AUTO_CREATE); + IntentFilter filter = new IntentFilter(MemorizingTrustManager.INTERCEPT_DECISION_INTENT + + "/" + getPackageName()); + filter.setPriority(RECEIVER_PRIORITY); + registerReceiver(mSslReceiver, filter); + + } + + /* (non-Javadoc) + * @see android.app.Activity#onPause() + */ + @Override + protected void onStop() { + super.onStop(); + if (mBinded && mTask.getStatus() != AsyncTask.Status.RUNNING) { + unbindService(mServConn); + mXmppFacade = null; + mBinded = false; + } + unregisterReceiver(mSslReceiver); + } + + /* (non-Javadoc) + * @see android.app.Activity#onKeyDown(int, android.view.KeyEvent) + */ + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + // TODO use onBackPressed on Eclair (2.0) + if (keyCode == KeyEvent.KEYCODE_BACK && mTask.getStatus() != AsyncTask.Status.FINISHED) { + if (!mTask.cancel(true)) { + Log.d(TAG, "Can't interrupt the connection"); + } + setResult(Activity.RESULT_CANCELED); + } + return super.onKeyDown(keyCode, event); + } + + /** + * Click event listener on cancel button. + */ + private class ClickListener implements OnClickListener { + + /** + * Constructor. + */ + ClickListener() { + } + + @Override + public void onClick(View v) { + if (v == mCancelBt) { + if (!mTask.cancel(true)) { + Log.d(TAG, "Can't interrupt the connection"); + } + setResult(Activity.RESULT_CANCELED); + finish(); + } + } + } + + /** + * Asynchronous class for connection. + */ + private class LoginTask extends LoginAsyncTask { + + /** + * Constructor. + */ + LoginTask() { + } + + /* (non-Javadoc) + * @see android.os.AsyncTask#onPostExecute(java.lang.Object) + */ + @Override + protected void onPostExecute(Boolean result) { + + if (result == null || !result) { // Task cancelled or exception + if (!result) { + Intent i = new Intent(); + i.putExtra("message", getErrorMessage()); + LoginAnim.this.setResult(Activity.RESULT_CANCELED, i); + } else + LoginAnim.this.setResult(Activity.RESULT_CANCELED); + LoginAnim.this.finish(); + } else { + mCancelBt.setEnabled(false); + LoginAnim.this.startService(LoginAnim.SERVICE_INTENT); + LoginAnim.this.setResult(Activity.RESULT_OK); + LoginAnim.this.finish(); + } + } + + @Override + protected void onProgressUpdate(Integer ... values) { + mLoginState.setText(getResources().getStringArray(R.array.loganim_state)[values[0]]); + } + + /* (non-Javadoc) + * @see android.os.AsyncTask#onCancelled() + */ + @Override + protected void onCancelled() { + super.onCancelled(); + LoginAnim.this.stopService(LoginAnim.SERVICE_INTENT); + } + + } + + /** + * The service connection used to connect to the Beem service. + */ + private class LoginServiceConnection implements ServiceConnection { + + /** + * Constructor. + */ + public LoginServiceConnection() { + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mXmppFacade = IXmppFacade.Stub.asInterface(service); + if (mTask.getStatus() == AsyncTask.Status.PENDING) + mTask = mTask.execute(mXmppFacade); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mXmppFacade = null; + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/PrivacyList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/PrivacyList.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,361 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui; + +import java.util.ArrayList; +import java.util.List; + +import android.app.Dialog; +import android.app.ListActivity; +import android.content.ComponentName; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.DialogInterface.OnDismissListener; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.ContextMenu; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; + +import com.beem.project.beem.R; +import com.beem.project.beem.service.PrivacyListItem; +import com.beem.project.beem.service.aidl.IPrivacyListListener; +import com.beem.project.beem.service.aidl.IPrivacyListManager; +import com.beem.project.beem.service.aidl.IXmppFacade; +import com.beem.project.beem.ui.dialogs.builders.CreatePrivacyList; +import com.beem.project.beem.ui.dialogs.builders.DeletePrivacyList; +import com.beem.project.beem.utils.BeemBroadcastReceiver; + +/** + * This class represents an activity which allows the user to manage his privacy lists. + * @author Jean-Manuel Da Silva + */ +public class PrivacyList extends ListActivity { + + private static final String TAG = "PrivacyList"; + private static final Intent SERVICE_INTENT = new Intent(); + static { + SERVICE_INTENT.setComponent(new ComponentName("com.beem.project.beem", "com.beem.project.beem.BeemService")); + } + + private static final int DIALOG_CREATE = 0; + private static final int DIALOG_UPDATE_BUDDIES = 1; + private static final int DIALOG_UPDATE_GROUPS = 2; + private static final int DIALOG_DELETE = 3; + + private static final String SAVED_INSTANCE_KEY_PRIVACY_LISTS = "PRIVACY_LISTS"; + + private Handler mHandler = new Handler(); + + private ArrayAdapter mAdapter; + private final List mPrivacyListNames = new ArrayList(); + private String mCurrPrivacyListName; + + private final ServiceConnection mConn = new BeemServiceConnection(); + private final BeemBroadcastReceiver mBroadcastReceiver = new BeemBroadcastReceiver(); + + private IPrivacyListManager mPrivacyListManager; + private IPrivacyListListener mPrivacyListListener; + + /** + * Constructor. + */ + public PrivacyList() { + super(); + } + + /** + * {@inheritDoc}. + */ + @Override + protected void onSaveInstanceState(Bundle savedInstanceState) { + Log.d(TAG, "BEGIN onSaveInstanceState."); + savedInstanceState.putStringArrayList(SAVED_INSTANCE_KEY_PRIVACY_LISTS, (ArrayList) mPrivacyListNames); + Log.d(TAG, "END onSaveInstanceState."); + super.onSaveInstanceState(savedInstanceState); + } + + /** + * {@inheritDoc}. + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.d(TAG, "BEGIN onCreate."); + + setContentView(R.layout.privacy_list); + registerForContextMenu(this.getListView()); + + mHandler = new Handler(); + + if (savedInstanceState != null && !savedInstanceState.isEmpty()) { + mPrivacyListNames.addAll(savedInstanceState.getStringArrayList(SAVED_INSTANCE_KEY_PRIVACY_LISTS)); + } + + mAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, mPrivacyListNames); + setListAdapter(mAdapter); + + bindService(SERVICE_INTENT, mConn, BIND_AUTO_CREATE); + + mPrivacyListListener = new PrivacyListListener(); + this.registerReceiver(mBroadcastReceiver, new IntentFilter(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED)); + + Log.d(TAG, "END onCreate."); + } + + /** + * {@inheritDoc}. + */ + @Override + protected void onDestroy() { + super.onDestroy(); + + Log.v(TAG, "BEGIN onDestroy."); + + if (mPrivacyListManager != null) { + try { + mPrivacyListManager.removePrivacyListListener(mPrivacyListListener); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + } + + this.unregisterReceiver(mBroadcastReceiver); + unbindService(mConn); + + Log.v(TAG, "END onDestroy."); + } + + /** + * {@inheritDoc}. + */ + @Override + protected void onStart() { + super.onStart(); + + Log.v(TAG, "BEGIN onStart."); + Log.v(TAG, "END onStart."); + } + + /** + * {@inheritDoc}. + */ + @Override + protected void onStop() { + super.onStop(); + + Log.v(TAG, "BEGIN onStop."); + Log.v(TAG, "END onStop."); + } + + /** + * {@inheritDoc}. + */ + @Override + protected Dialog onCreateDialog(int id) { + Dialog dialog; + switch (id) { + case DIALOG_CREATE: + dialog = new CreatePrivacyList(PrivacyList.this, mPrivacyListManager).create(); + dialog.setOnDismissListener(new OnDismissListener() { + + @Override + public void onDismiss(DialogInterface dialog) { + PrivacyList.this.removeDialog(DIALOG_CREATE); + } + }); + break; + case DIALOG_DELETE: + dialog = new DeletePrivacyList(PrivacyList.this, mPrivacyListManager, mCurrPrivacyListName).create(); + dialog.setOnDismissListener(new OnDismissListener() { + + @Override + public void onDismiss(DialogInterface dialog) { + PrivacyList.this.removeDialog(DIALOG_DELETE); + } + + }); + break; + default: + dialog = null; + } + return dialog; + } + + /** + * {@inheritDoc}. + */ + @Override + public final boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.privacy_list, menu); + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.privacy_list_context, menu); + mCurrPrivacyListName = mPrivacyListNames.get(((AdapterView.AdapterContextMenuInfo) menuInfo).position); + menu.setHeaderTitle(mCurrPrivacyListName); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.privacy_list_context_menu_buddies_item: + return true; + case R.id.privacy_list_context_menu_groups_item: + return true; + case R.id.privacy_list_context_menu_delete_item: + showDialog(DIALOG_DELETE); + return true; + default: + return super.onContextItemSelected(item); + } + } + + /** + * {@inheritDoc}. + */ + @Override + public final boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.privacy_list_menu_create: + showDialog(DIALOG_CREATE); + return true; + default: + return false; + } + } + + /** + * Service connection. + * @author jamu + */ + private final class BeemServiceConnection implements ServiceConnection { + + private IXmppFacade mXmppFacade; + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + Log.v(TAG, "BEGIN onServiceConnected."); + mXmppFacade = IXmppFacade.Stub.asInterface(service); + try { + mPrivacyListManager = mXmppFacade.getPrivacyListManager(); + mPrivacyListManager.addPrivacyListListener(mPrivacyListListener); + mPrivacyListNames.clear(); + mPrivacyListNames.addAll(mPrivacyListManager.getPrivacyLists()); + mAdapter.notifyDataSetChanged(); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + Log.v(TAG, "END onServiceConnected."); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.v(TAG, "BEGIN onServiceDisconnected."); + mXmppFacade = null; + try { + mPrivacyListManager.removePrivacyListListener(mPrivacyListListener); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + Log.v(TAG, "END onServiceDisconnected."); + } + } + + /** + * Listener. + * @author jamu + */ + private class PrivacyListListener extends IPrivacyListListener.Stub { + + @Override + public void setPrivacyList(String listName, List listItem) throws RemoteException { + Log.d(TAG, "BEGIN PrivacyListListener >> setPrivacyList."); + Log.d(TAG, "> " + listName + " has been setted."); + Log.d(TAG, "END PrivacyListListener >> setPrivacyList."); + } + + @Override + public void updatedPrivacyList(final String listName) throws RemoteException { + Log.d(TAG, "BEGIN PrivacyListListener >> updatedPrivacyList."); + mHandler.post(new Runnable() { + @Override + public void run() { + try { + mPrivacyListNames.clear(); + // Not that much lists and require some server queries to know if the list has been + // updated/deleted or set to default/active by this activity or another IM client. + mPrivacyListNames.addAll(mPrivacyListManager.getPrivacyLists()); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + mAdapter.notifyDataSetChanged(); + } + }); + Log.d(TAG, "END PrivacyListListener >> updatedPrivacyList."); + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/Settings.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/Settings.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,120 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui; + +import android.content.ComponentName; +import android.content.Intent; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import com.beem.project.beem.BeemApplication; +import com.beem.project.beem.R; +import com.beem.project.beem.ui.wizard.Account; + +/** + * This class represents an activity which allows the user to change his account or proxy parameters. + */ +public class Settings extends PreferenceActivity { + + private static final Intent SERVICE_INTENT = new Intent(); + + static { + SERVICE_INTENT.setComponent(new ComponentName("com.beem.project.beem", "com.beem.project.beem.BeemService")); + } + + /** + * Constructor. + */ + public Settings() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); + } + + @Override + protected void onStart() { + super.onStart(); + String accountName = getPreferenceManager().getDefaultSharedPreferences(this) + .getString(BeemApplication.ACCOUNT_USERNAME_KEY, ""); + Preference account = findPreference(BeemApplication.ACCOUNT_USERNAME_KEY); + account.setSummary(accountName); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater mInflater = getMenuInflater(); + mInflater.inflate(R.menu.edit_settings, menu); + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + Intent i = null; + switch (item.getItemId()) { + case R.id.settings_menu_create_account: + i = new Intent(this, Account.class); + startActivity(i); + return true; + case R.id.settings_menu_privacy_lists: + i = new Intent(this, PrivacyList.class); + startActivity(i); + return true; + default: + return false; + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/Subscription.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/Subscription.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,208 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui; + +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.Presence.Type; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.TextView; +import android.widget.Toast; + +import com.beem.project.beem.BeemService; +import com.beem.project.beem.R; +import com.beem.project.beem.service.PresenceAdapter; +import com.beem.project.beem.service.aidl.IXmppFacade; +import com.beem.project.beem.service.Contact; +import com.beem.project.beem.utils.BeemBroadcastReceiver; + +/** + * This activity is used to accept a subscription request. + * @author nikita + */ +public class Subscription extends Activity { + + private static final Intent SERVICE_INTENT = new Intent(); + private static final String TAG = Subscription.class.getSimpleName(); + private IXmppFacade mService; + private String mContact; + private ServiceConnection mServConn = new BeemServiceConnection(); + private final BeemBroadcastReceiver mReceiver = new BeemBroadcastReceiver(); + private MyOnClickListener mClickListener = new MyOnClickListener(); + + static { + SERVICE_INTENT.setComponent(new ComponentName("com.beem.project.beem", "com.beem.project.beem.BeemService")); + } + + /** + * Constructor. + */ + public Subscription() { + } + + /* (non-Javadoc) + * @see android.app.Activity#onCreate(android.os.Bundle) + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.subscription); + findViewById(R.id.SubscriptionAccept).setOnClickListener(mClickListener); + findViewById(R.id.SubscriptionRefuse).setOnClickListener(mClickListener); + Contact c = new Contact(getIntent().getData()); + mContact = c.getJID(); + TextView tv = (TextView) findViewById(R.id.SubscriptionText); + String str = String.format(getString(R.string.SubscriptText), mContact); + tv.setText(str); + this.registerReceiver(mReceiver, new IntentFilter(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED)); + } + + /* (non-Javadoc) + * @see android.app.Activity#onResume() + */ + @Override + protected void onResume() { + super.onResume(); + bindService(new Intent(this, BeemService.class), mServConn, BIND_AUTO_CREATE); + } + + /* (non-Javadoc) + * @see android.app.Activity#onPause() + */ + @Override + protected void onPause() { + super.onPause(); + unbindService(mServConn); + } + + /* (non-Javadoc) + * @see android.app.Activity#onDestroy() + */ + @Override + protected void onDestroy() { + super.onDestroy(); + this.unregisterReceiver(mReceiver); + } + + /** + * Send the presence stanza. + * + * @param p presence stanza + */ + private void sendPresence(Presence p) { + PresenceAdapter preAdapt = new PresenceAdapter(p); + try { + mService.sendPresencePacket(preAdapt); + } catch (RemoteException e) { + Log.e(TAG, "Error while sending subscription response", e); + } + } + + /** + * Event simple click on buttons. + */ + private class MyOnClickListener implements OnClickListener { + + /** + * Constructor. + */ + public MyOnClickListener() { + } + + @Override + public void onClick(View v) { + Presence presence = null; + switch (v.getId()) { + case R.id.SubscriptionAccept: + presence = new Presence(Type.subscribed); + Toast.makeText(Subscription.this, getString(R.string.SubscriptAccept), Toast.LENGTH_SHORT) + .show(); + break; + case R.id.SubscriptionRefuse: + presence = new Presence(Type.unsubscribed); + Toast.makeText(Subscription.this, getString(R.string.SubscriptRefused), Toast.LENGTH_SHORT).show(); + break; + default: + Toast.makeText(Subscription.this, getString(R.string.SubscriptError), Toast.LENGTH_SHORT).show(); + } + if (presence != null) { + presence.setTo(mContact); + sendPresence(presence); + } + finish(); + } + }; + + /** + * The ServiceConnection used to connect to the Beem service. + */ + private class BeemServiceConnection implements ServiceConnection { + + /** + * Constructor. + */ + public BeemServiceConnection() { + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mService = IXmppFacade.Stub.asInterface(service); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mService = null; + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/dialogs/builders/Alias.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/dialogs/builders/Alias.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,121 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui.dialogs.builders; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.RemoteException; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; + +import com.beem.project.beem.R; +import com.beem.project.beem.service.Contact; +import com.beem.project.beem.service.aidl.IRoster; + +/** + * Create dialog alias. + */ +public class Alias extends AlertDialog.Builder { + + private static final String TAG = "Dialogs.Builders > Alias"; + + private IRoster mRoster; + private Contact mContact; + private EditText mEditTextAlias; + + /** + * Constructor. + * @param context context activity. + * @param roster Beem roster. + * @param contact the contact to modify. + */ + public Alias(final Context context, final IRoster roster, final Contact contact) { + super(context); + + mRoster = roster; + mContact = contact; + + LayoutInflater factory = LayoutInflater.from(context); + final View textEntryView = factory.inflate( + R.layout.contactdialogaliasdialog, null); + setTitle(mContact.getJID()); + setView(textEntryView); + mEditTextAlias = (EditText) textEntryView.findViewById( + R.id.CDAliasDialogName); + mEditTextAlias.setText(mContact.getName()); + setPositiveButton(R.string.OkButton, new DialogClickListener()); + setNegativeButton(R.string.CancelButton, new DialogClickListener()); + } + + /** + * Event click listener. + */ + class DialogClickListener implements DialogInterface.OnClickListener { + + /** + * Constructor. + */ + public DialogClickListener() { + } + + + @Override + public void onClick(final DialogInterface dialog, final int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + String name = mEditTextAlias.getText().toString(); + if (name.length() == 0) { + name = mContact.getJID(); + } + try { + mRoster.setContactName(mContact.getJID(), name); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + } + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/dialogs/builders/ChatList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/dialogs/builders/ChatList.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,90 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui.dialogs.builders; + +import java.util.List; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; + +import com.beem.project.beem.R; +import com.beem.project.beem.service.Contact; + +/** + * Create the change chat dialog. + */ +public class ChatList extends AlertDialog.Builder { + + //private static final String TAG = "Dialogs.Builders > Chat list"; + + /** + * Constructor. + * @param context context activity. + * @param openedChats A list containing the JID of participants of the opened chats. + */ + public ChatList(final Context context, final List openedChats) { + super(context); + + if (openedChats.size() > 0) { + CharSequence[] items = new CharSequence[openedChats.size()]; + + int i = 0; + for (Contact c : openedChats) { + items[i++] = c.getName(); + } + setTitle(R.string.chat_dialog_change_chat_title); + setItems(items, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + Intent chatIntent = new Intent(context, com.beem.project.beem.ui.Chat.class); + chatIntent.setData((openedChats.get(item)).toUri()); + context.startActivity(chatIntent); + } + }); + } else { + setMessage(R.string.chat_no_more_chats); + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/dialogs/builders/CreatePrivacyList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/dialogs/builders/CreatePrivacyList.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,120 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui.dialogs.builders; + +import java.util.ArrayList; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.RemoteException; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; + +import com.beem.project.beem.R; +import com.beem.project.beem.service.PrivacyListItem; +import com.beem.project.beem.service.aidl.IPrivacyListManager; + +/** + * Use this builder to build a dialog which handles a privacy list creation. + * @author Jean-Manuel Da Silva + */ +public class CreatePrivacyList extends AlertDialog.Builder { + + private static final String TAG = "Dialogs.Builders > CreatePrivacyList"; + + private final IPrivacyListManager mPrivacyListManager; + private final View mTextEntryView; + private EditText mListNameField; + + /** + * Constructor. + * @param context context activity. + * @param privacyListManager the privacy list manager that will be use to create our list. + */ + public CreatePrivacyList(final Context context, final IPrivacyListManager privacyListManager) { + super(context); + + LayoutInflater factory = LayoutInflater.from(context); + + mTextEntryView = factory.inflate(R.layout.privacy_list_create_dialog, null); + setView(mTextEntryView); + + mPrivacyListManager = privacyListManager; + mListNameField = (EditText) mTextEntryView.findViewById(R.id.privacy_list_create_dialog_list_name); + + setTitle(R.string.privacy_list_create_dialog_title); + setPositiveButton(R.string.privacy_list_create_dialog_create_button, new DialogClickListener()); + setNegativeButton(R.string.CancelButton, new DialogClickListener()); + } + + /** + * Event click listener. + */ + class DialogClickListener implements DialogInterface.OnClickListener { + + /** + * Constructor. + */ + public DialogClickListener() { + } + + @Override + public void onClick(final DialogInterface dialog, final int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + try { + Log.d(TAG, "mPrivacyListManager ## " + mPrivacyListManager); + Log.d(TAG, "listNameField ## " + mListNameField); + Log.d(TAG, "listNameField.getText().toString() ## " + mListNameField.getText().toString()); + mPrivacyListManager.createPrivacyList(mListNameField.getText().toString(), + new ArrayList()); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + } + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/dialogs/builders/DeleteContact.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/dialogs/builders/DeleteContact.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,107 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui.dialogs.builders; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.RemoteException; +import android.util.Log; + +import com.beem.project.beem.R; +import com.beem.project.beem.service.Contact; +import com.beem.project.beem.service.aidl.IRoster; + +/** + * Use this builder to build a dialog which allows you to delete a contact from a specific roster. + * @author Jean-Manuel Da Silva + */ +public class DeleteContact extends AlertDialog.Builder { + + private static final String TAG = "Dialogs.Builders > DeleteContact"; + + private IRoster mRoster; + private Contact mContact; + + /** + * Constructor. + * @param context context activity. + * @param roster the roster which has the contact you want to delete. + * @param contact the contact to delete. + */ + public DeleteContact(final Context context, final IRoster roster, final Contact contact) { + super(context); + + mContact = contact; + mRoster = roster; + + setMessage(R.string.userinfo_sure2delete); + DialogClickListener dl = new DialogClickListener(); + setPositiveButton(R.string.userinfo_yes, dl); + setNegativeButton(R.string.userinfo_no, dl); + } + + /** + * Event click listener. + */ + private class DialogClickListener implements DialogInterface.OnClickListener { + + /** + * Constructor. + */ + public DialogClickListener() { + } + + @Override + public void onClick(final DialogInterface dialog, final int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + try { + mRoster.deleteContact(mContact); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + } + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/dialogs/builders/DeletePrivacyList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/dialogs/builders/DeletePrivacyList.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,107 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui.dialogs.builders; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.RemoteException; +import android.util.Log; + +import com.beem.project.beem.R; +import com.beem.project.beem.service.aidl.IPrivacyListManager; + +/** + * Use this builder to build a dialog which allows you to delete a privacy list. + * @author Jean-Manuel Da Silva + */ +public class DeletePrivacyList extends AlertDialog.Builder { + + private static final String TAG = "Dialogs.Builders > DeletePrivacyList"; + + private final IPrivacyListManager mPrivacyListManager; + private final String mPrivacyListName; + + /** + * Constructor. + * @param context context activity. + * @param privacyListManager the privacy list manager managing the privacy list you want to delete. + * @param privacyListName the name of the privacy list you want to delete. + */ + public DeletePrivacyList(final Context context, final IPrivacyListManager privacyListManager, + final String privacyListName) { + super(context); + + mPrivacyListManager = privacyListManager; + mPrivacyListName = privacyListName; + + setMessage(context.getString(R.string.privacy_list_delete_dialog_msg, privacyListName)); + DialogClickListener dl = new DialogClickListener(); + setPositiveButton(R.string.privacy_list_delete_dialog_yes, dl); + setNegativeButton(R.string.privacy_list_delete_dialog_no, dl); + } + + /** + * Event click listener. + */ + private class DialogClickListener implements DialogInterface.OnClickListener { + + /** + * Constructor. + */ + public DialogClickListener() { + } + + @Override + public void onClick(final DialogInterface dialog, final int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + try { + mPrivacyListManager.removePrivacyList(mPrivacyListName); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + } + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/dialogs/builders/DisplayOtrFingerprint.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/dialogs/builders/DisplayOtrFingerprint.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,111 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui.dialogs.builders; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.RemoteException; + +import com.beem.project.beem.R; +import com.beem.project.beem.service.aidl.IChat; + +/** + * Use this builder to build a dialog which allows you to display otr fingerprints. + * @author nikita + */ +public class DisplayOtrFingerprint extends AlertDialog.Builder { + + private static final String TAG = "DisplayOtrFingerprint"; + private IChat mChat; + + /** + * Constructor. + * @param context context activity. + * @param chat the current chat. + */ + public DisplayOtrFingerprint(final Context context, final IChat chat) { + super(context); + + mChat = chat; + try { + setMessage(context.getString(R.string.chat_otr_verify_key, chat.getLocalOtrFingerprint(), + chat.getRemoteOtrFingerprint())); + } catch (RemoteException e) { + e.printStackTrace(); + } + DialogClickListener dl = new DialogClickListener(); + setPositiveButton(R.string.userinfo_yes, dl); + setNegativeButton(R.string.userinfo_no, dl); + } + + /** + * Event click listener. + */ + private class DialogClickListener implements DialogInterface.OnClickListener { + + /** + * Constructor. + */ + public DialogClickListener() { + } + + @Override + public void onClick(final DialogInterface dialog, final int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + try { + mChat.verifyRemoteFingerprint(true); + } catch (RemoteException e) { + e.printStackTrace(); + } + } else if (which == DialogInterface.BUTTON_NEGATIVE) { + try { + mChat.verifyRemoteFingerprint(false); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/dialogs/builders/ResendSubscription.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/dialogs/builders/ResendSubscription.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,117 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui.dialogs.builders; + +import org.jivesoftware.smack.packet.Presence; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.RemoteException; +import android.util.Log; +import android.widget.Toast; + +import com.beem.project.beem.R; +import com.beem.project.beem.service.Contact; +import com.beem.project.beem.service.PresenceAdapter; +import com.beem.project.beem.service.aidl.IXmppFacade; + +/** + * Use this builder to build a dialog which allows you resend a subscription query to a contact. + * @author Jean-Manuel Da Silva + */ +public class ResendSubscription extends AlertDialog.Builder { + + private static final String TAG = "Dialogs.Builders > ResendSubscription"; + + private Context mContext; + private IXmppFacade mXmppFacade; + private Contact mContact; + + /** + * Constructor. + * @param context context activity. + * @param xmppFacade the XMPP Facade used to send the query. + * @param contact the receiver of the query. + */ + public ResendSubscription(final Context context, final IXmppFacade xmppFacade, final Contact contact) { + super(context); + + mContext = context; + mXmppFacade = xmppFacade; + mContact = contact; + + setMessage(R.string.userinfo_sureresend); + DialogClickListener dl = new DialogClickListener(); + setPositiveButton(R.string.userinfo_yes, dl); + setNegativeButton(R.string.userinfo_no, dl); + } + + /** + * Event click listener. + */ + class DialogClickListener implements DialogInterface.OnClickListener { + + /** + * Constructor. + */ + DialogClickListener() { + } + + @Override + public void onClick(final DialogInterface dialog, final int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + Presence presencePacket = new Presence(Presence.Type.subscribe); + presencePacket.setTo(mContact.getJID()); + try { + mXmppFacade.sendPresencePacket(new PresenceAdapter(presencePacket)); + Toast.makeText(mContext, mContext.getString(R.string.userinfo_resend), Toast.LENGTH_SHORT).show(); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage()); + } + } + } + + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/dialogs/builders/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/dialogs/builders/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,48 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +/** + * This package contains Beem dialog builder's. + */ +package com.beem.project.beem.ui.dialogs.builders; + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,47 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. +*/ +/** + * This package contains the different activity displayed by BEEM and other class useful to make the UI. + */ +package com.beem.project.beem.ui; + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/views/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/views/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,31 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2011 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ +/** + * This package contains the custom View used by Beem to make the user interfaces. + */ +package com.beem.project.beem.ui.views; + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/wizard/Account.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/wizard/Account.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,169 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui.wizard; + +import android.app.Activity; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.RadioGroup; + +import com.beem.project.beem.R; + +/** + * The first activity of an user friendly wizard to configure a XMPP account. + */ +public class Account extends FragmentActivity { + + private static final String TAG = Account.class.getSimpleName(); + + private FragmentManager fragmentMgr; + + /** + * Constructor. + */ + public Account() { + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + fragmentMgr = getSupportFragmentManager(); + if (savedInstanceState == null) { + FragmentTransaction t = fragmentMgr.beginTransaction(); + t.add(android.R.id.content, MainFragment.newInstance(), "Main"); + t.commit(); + + } + } + + /** + * Callback called when the create account option is selected. + * + */ + public void onCreateAccountSelected() { + Fragment f = CreateAccountFragment.newInstance(); + FragmentTransaction transaction = fragmentMgr.beginTransaction(); + + transaction.replace(android.R.id.content, f, "createAccount"); + transaction.addToBackStack(null); + transaction.commit(); + } + + /** + * Callback called when the configure account option is selected. + * + */ + public void onConfigureAccountSelected() { + Fragment f = AccountConfigureFragment.newInstance(); + FragmentTransaction transaction = fragmentMgr.beginTransaction(); + + transaction.replace(android.R.id.content, f, "configureAccount"); + transaction.addToBackStack(null); + transaction.commit(); + } + + /** + * Main fragment of the wizard account activity. + */ + public static class MainFragment extends Fragment implements OnClickListener, RadioGroup.OnCheckedChangeListener { + private RadioGroup mConfigureGroup; + private Button mNextButton; + + private Account activity; + + /** + * Create a new MainFragment. + * + * @return a MainFragment + */ + static MainFragment newInstance() { + return new MainFragment(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.wizard_account_main_fragment, container, false); + mNextButton = (Button) v.findViewById(R.id.next); + mNextButton.setOnClickListener(this); + mConfigureGroup = (RadioGroup) v.findViewById(R.id.configure_group); + mConfigureGroup.setOnCheckedChangeListener(this); + return v; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + this.activity = (Account) activity; + } + + @Override + public void onClick(View v) { + if (v == mNextButton) { + int selectedid = mConfigureGroup.getCheckedRadioButtonId(); + if (selectedid == R.id.configure_account) { + activity.onConfigureAccountSelected(); + } else if (selectedid == R.id.create_account) { + activity.onCreateAccountSelected(); + } + } + } + + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + if (checkedId == -1) + mNextButton.setEnabled(false); + else + mNextButton.setEnabled(true); + } + + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/wizard/AccountConfigureFragment.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/wizard/AccountConfigureFragment.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,530 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui.wizard; + +import android.accounts.AccountManager; +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.InputFilter; +import android.text.LoginFilter; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import com.beem.project.beem.BeemApplication; +import com.beem.project.beem.R; +import com.beem.project.beem.ui.Login; +import com.beem.project.beem.ui.Settings; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.ConnectionConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.proxy.ProxyInfo; +import org.jivesoftware.smack.proxy.ProxyInfo.ProxyType; +import org.jivesoftware.smack.util.StringUtils; + +/** + * Fragment to enter the information required in order to configure a XMPP account. + * + */ +public class AccountConfigureFragment extends Fragment implements OnClickListener { + + private static final String TAG = AccountConfigureFragment.class.getSimpleName(); + + private static final String GOOGLE_ACCOUNT_TYPE = "com.google"; + private static final int SELECT_ACCOUNT_CODE = 1; + private static final int MANUAL_CONFIGURATION_CODE = 2; + + private Button mNextButton; + private Button mManualConfigButton; + private Button mSelectAccountButton; + private TextView mErrorLabel; + private TextView mSettingsWarningLabel; + private EditText mAccountJID; + private EditText mAccountPassword; + private final JidTextWatcher mJidTextWatcher = new JidTextWatcher(); + private final PasswordTextWatcher mPasswordTextWatcher = new PasswordTextWatcher(); + private boolean mValidJid; + private boolean mValidPassword; + private String mSelectedAccountName; + private String mSelectedAccountType; + private SharedPreferences settings; + private boolean useSystemAccount; + + private com.beem.project.beem.ui.wizard.AccountConfigureFragment.ConnectionTestTask task; + + /** + * Create a new AccountConfigureFragment. + * + * @return a new AccountConfigureFragment + */ + public static AccountConfigureFragment newInstance() { + return new AccountConfigureFragment(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.d(TAG, "onCreate"); + setRetainInstance(true); + + settings = PreferenceManager.getDefaultSharedPreferences(getActivity()); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.wizard_account_configure, container, false); + mManualConfigButton = (Button) v.findViewById(R.id.manual_setup); + mManualConfigButton.setOnClickListener(this); + mNextButton = (Button) v.findViewById(R.id.next); + mNextButton.setOnClickListener(this); + mSelectAccountButton = (Button) v.findViewById(R.id.select_account_btn); + mSelectAccountButton.setOnClickListener(this); + mErrorLabel = (TextView) v.findViewById(R.id.error_label); + mSettingsWarningLabel = (TextView) v.findViewById(R.id.settings_warn_label); + mAccountJID = (EditText) v.findViewById(R.id.account_username); + mAccountPassword = (EditText) v.findViewById(R.id.account_password); + InputFilter[] orgFilters = mAccountJID.getFilters(); + InputFilter[] newFilters = new InputFilter[orgFilters.length + 1]; + int i; + for (i = 0; i < orgFilters.length; i++) + newFilters[i] = orgFilters[i]; + newFilters[i] = new LoginFilter.UsernameFilterGeneric(); + mAccountJID.setFilters(newFilters); + mAccountJID.addTextChangedListener(mJidTextWatcher); + mAccountPassword.addTextChangedListener(mPasswordTextWatcher); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) // true to disable the feature until ready + v.findViewById(R.id.account_layout).setVisibility(View.GONE); + return v; + } + + @Override + public void onStart() { + super.onStart(); + useSystemAccount = settings.getBoolean(BeemApplication.USE_SYSTEM_ACCOUNT_KEY, false); + // temporaly disable jid watcher + mAccountJID.removeTextChangedListener(mJidTextWatcher); + mAccountJID.setText(settings.getString(BeemApplication.ACCOUNT_USERNAME_KEY, "")); + if (useSystemAccount) { + mAccountPassword.setText("*******"); //dummy password + mAccountJID.setText(settings.getString(BeemApplication.ACCOUNT_USERNAME_KEY, "")); + mAccountPassword.setEnabled(false); + mNextButton.setEnabled(true); + } + mAccountJID.addTextChangedListener(mJidTextWatcher); + if (settings.getBoolean(BeemApplication.ACCOUNT_SPECIFIC_SERVER_KEY, false) + || settings.getBoolean(BeemApplication.PROXY_USE_KEY, false)) { + mSettingsWarningLabel.setVisibility(View.VISIBLE); + } else + mSettingsWarningLabel.setVisibility(View.GONE); + + } + + @TargetApi(14) + @Override + public void onClick(View v) { + if (v == mNextButton) { + if (useSystemAccount) { + onDeviceAccountSelected(settings.getString(BeemApplication.ACCOUNT_USERNAME_KEY, ""), + settings.getString(BeemApplication.ACCOUNT_SYSTEM_TYPE_KEY, "")); + } else { + String jid = mAccountJID.getText().toString(); + jid = StringUtils.parseBareAddress(jid); + String password = mAccountPassword.getText().toString(); + task = new ConnectionTestTask(); + if (settings.getBoolean(BeemApplication.ACCOUNT_SPECIFIC_SERVER_KEY, false)) { + String server = settings.getString(BeemApplication.ACCOUNT_SPECIFIC_SERVER_HOST_KEY, ""); + String port = settings.getString(BeemApplication.ACCOUNT_SPECIFIC_SERVER_PORT_KEY, "5222"); + task.execute(jid, password, server, port); + } else + task.execute(jid, password); + } + } else if (v == mManualConfigButton) { + onManualConfigurationSelected(); + } else if (v == mSelectAccountButton) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + Intent i = AccountManager.newChooseAccountIntent(null, null, + new String[] {GOOGLE_ACCOUNT_TYPE}, true, null, null, null, null); + startActivityForResult(i, SELECT_ACCOUNT_CODE); + } + } + } + + /** + * Callback called when the Manual configuration button is selected. + * + */ + public void onManualConfigurationSelected() { + Intent i = new Intent(getActivity(), Settings.class); + i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivityForResult(i, MANUAL_CONFIGURATION_CODE); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == SELECT_ACCOUNT_CODE && resultCode == Activity.RESULT_OK) { + mSelectedAccountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); + mSelectedAccountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); + onDeviceAccountSelected(mSelectedAccountName, mSelectedAccountType); + } else if (requestCode == MANUAL_CONFIGURATION_CODE) { + String login = settings.getString(BeemApplication.ACCOUNT_USERNAME_KEY, ""); + String password = settings.getString(BeemApplication.ACCOUNT_PASSWORD_KEY, ""); + mAccountJID.setText(login); + mAccountPassword.setText(password); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } + + /** + * Callback called when the account was connected successfully. + * + * @param jid the jid used to connect + * @param password the password used to connect + * + */ + private void onAccountConnectionSuccess(String jid, String password) { + Activity a = getActivity(); + saveCredential(jid, password); + // launch login + Intent i = new Intent(a, Login.class); + i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(i); + a.finish(); + } + + /** + * Callback called when the account connection failed. + * + */ + private void onAccountConnectionFailed() { + mAccountPassword.setText(""); + mErrorLabel.setVisibility(View.VISIBLE); + } + + /** + * Callback called when the user select an account from the device (Android Account api). + * + * @param accountName the account name + * @param accountType the account type + * + */ + private void onDeviceAccountSelected(String accountName, String accountType) { + Activity a = getActivity(); + saveCredentialAccount(accountName, accountType); + // launch login + Intent i = new Intent(a, Login.class); + i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(i); + a.finish(); + } + + /** + * Save the user credentials. + * + * @param jid the jid of the user + * @param pass the password of the user + * + */ + private void saveCredential(String jid, String pass) { + SharedPreferences.Editor edit = settings.edit(); + edit.putString(BeemApplication.ACCOUNT_USERNAME_KEY, jid); + edit.putString(BeemApplication.ACCOUNT_PASSWORD_KEY, pass); + edit.putBoolean(BeemApplication.USE_SYSTEM_ACCOUNT_KEY, false); + edit.commit(); + } + + /** + * Save the user credentials. + * + * @param accountName the account name of the user + * @param accountType the account type of the user + * + */ + private void saveCredentialAccount(String accountName, String accountType) { + SharedPreferences.Editor edit = settings.edit(); + edit.putString(BeemApplication.ACCOUNT_USERNAME_KEY, accountName); + edit.putString(BeemApplication.ACCOUNT_SYSTEM_TYPE_KEY, accountType); + edit.putBoolean(BeemApplication.USE_SYSTEM_ACCOUNT_KEY, true); + edit.commit(); + } + + /** + * Check that the username is really a JID. + * @param username the username to check. + */ + private void checkUsername(String username) { + String name = StringUtils.parseName(username); + String server = StringUtils.parseServer(username); + if (TextUtils.isEmpty(name) || TextUtils.isEmpty(server)) { + mValidJid = false; + } else { + mValidJid = true; + } + } + + /** + * Check password. + * @param password the password to check. + */ + private void checkPassword(String password) { + if (password.length() > 0) + mValidPassword = true; + else + mValidPassword = false; + } + + /** + * Text watcher to test the existence of a password. + */ + private class PasswordTextWatcher implements TextWatcher { + + /** + * Constructor. + */ + public PasswordTextWatcher() { + } + + @Override + public void afterTextChanged(Editable s) { + checkPassword(s.toString()); + mNextButton.setEnabled(mValidJid && mValidPassword); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + } + + /** + * TextWatcher to check the validity of a JID. + */ + private class JidTextWatcher implements TextWatcher { + + /** + * Constructor. + */ + public JidTextWatcher() { + } + + @Override + public void afterTextChanged(Editable s) { + checkUsername(s.toString()); + mNextButton.setEnabled(mValidJid && mValidPassword); + if (useSystemAccount) { + mAccountPassword.setEnabled(true); + mAccountPassword.setText(""); + } + useSystemAccount = false; + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + } + + /** + * AsyncTask use to test the credentials. + */ + class ConnectionTestTask extends AsyncTask { + + private ProgressFragment progress; + private ConnectionConfiguration config; + private XMPPException exception; + private String jid; + private String password; + private String server; + + @Override + protected void onPreExecute() { + mErrorLabel.setVisibility(View.INVISIBLE); + progress = ProgressFragment.newInstance(); + progress.show(getFragmentManager(), "progressFragment"); + } + + @Override + protected void onPostExecute(Boolean result) { + if (result) { + onAccountConnectionSuccess(jid, password); + } else { + onAccountConnectionFailed(); + } + ProgressFragment pf = (ProgressFragment) getFragmentManager().findFragmentByTag("progressFragment"); + if (pf != null) + pf.dismiss(); + } + + @Override + protected Boolean doInBackground(String... params) { + Log.d(TAG, "Xmpp login task"); + jid = params[0]; + password = params[1]; + + int port = -1; + if (params.length > 2) { + server = params[2]; + } + if (params.length > 3) { + if (!TextUtils.isEmpty(params[3])) { + port = Integer.parseInt(params[3]); + } + } + Log.d(TAG, "jid " + jid + " server " + server + " port " + port); + String login = StringUtils.parseName(jid); + String serviceName = StringUtils.parseServer(jid); + Connection connection = prepareConnection(jid, server, port); + if (settings.getBoolean(BeemApplication.FULL_JID_LOGIN_KEY, false) + || "gmail.com".equals(serviceName) + || "googlemail.com".equals(serviceName)) { + login = jid; + } + try { + connection.connect(); + connection.login(login, password); + } catch (XMPPException e) { + Log.e(TAG, "Unable to connect to Xmpp server", e); + exception = e; + return false; + } finally { + connection.disconnect(); + } + return true; + } + + /** + * Initialize the XMPP connection. + * + * @param jid the jid to use + * @param server the server to use (not using dns srv) may be null + * @param port the port + * + * @return the XMPPConnection prepared to connect + */ + private Connection prepareConnection(String jid, String server, int port) { + boolean useProxy = settings.getBoolean(BeemApplication.PROXY_USE_KEY, false); + ProxyInfo proxyinfo = ProxyInfo.forNoProxy(); + if (useProxy) { + String stype = settings.getString(BeemApplication.PROXY_TYPE_KEY, "HTTP"); + String phost = settings.getString(BeemApplication.PROXY_SERVER_KEY, ""); + String puser = settings.getString(BeemApplication.PROXY_USERNAME_KEY, ""); + String ppass = settings.getString(BeemApplication.PROXY_PASSWORD_KEY, ""); + int pport = Integer.parseInt(settings.getString(BeemApplication.PROXY_PORT_KEY, "1080")); + ProxyInfo.ProxyType type = ProxyType.valueOf(stype); + proxyinfo = new ProxyInfo(type, phost, pport, puser, ppass); + } + String serviceName = StringUtils.parseServer(jid); + if (port != -1 || !TextUtils.isEmpty(server)) { + if (port == -1) + port = 5222; + if (TextUtils.isEmpty(server)) + server = serviceName; + config = new ConnectionConfiguration(server, port, serviceName, proxyinfo); + } else { + config = new ConnectionConfiguration(serviceName, proxyinfo); + } + if (settings.getBoolean(BeemApplication.SMACK_DEBUG_KEY, false)) + config.setDebuggerEnabled(true); + config.setSendPresence(false); + config.setRosterLoadedAtLogin(false); + return new XMPPConnection(config); + } + } + + /** + * A progress Fragment. + */ + public static class ProgressFragment extends DialogFragment { + + /** + * Create a new ProgressFragment. + * + * @return a ProgressFragment + */ + public static ProgressFragment newInstance() { + return new ProgressFragment(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setCancelable(false); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Log.d(TAG, "create progress dialog"); + ProgressDialog p = new ProgressDialog(getActivity()); + p.setTitle(getString(R.string.login_login_progress)); + p.setMessage(getString(R.string.create_account_progress_message)); + return p; + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/wizard/CreateAccountFragment.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/wizard/CreateAccountFragment.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,399 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2012 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ +package com.beem.project.beem.ui.wizard; + +import java.util.regex.Pattern; + +import android.app.Activity; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import com.beem.project.beem.BeemApplication; +import com.beem.project.beem.R; +import com.beem.project.beem.ui.Login; + +import org.jivesoftware.smack.AccountManager; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.ConnectionConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.proxy.ProxyInfo; +import org.jivesoftware.smack.proxy.ProxyInfo.ProxyType; +import org.jivesoftware.smack.util.StringUtils; + + +/** + * Fragment used to create an account on an XMPP server. + */ +public class CreateAccountFragment extends Fragment implements android.view.View.OnClickListener { + private static final String TAG = CreateAccountFragment.class.getSimpleName(); + private EditText username; + private EditText password; + private EditText confirmPassword; + private TextView errorText; + private TextView mSettingsWarningLabel; + private AutoCompleteTextView serverEdit; + private Button createButton; + private CreateAccountTask task; + private final NotEmptyTextWatcher mTextWatcher = new NotEmptyTextWatcher(); + private SharedPreferences settings; + + /** + * Create a CreateAccountFragment. + * + */ + public CreateAccountFragment() { + } + + /** + * Create a CreateAccountFragment. + * @return a new CreateAccountFragment + */ + public static CreateAccountFragment newInstance() { + return new CreateAccountFragment(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + settings = PreferenceManager.getDefaultSharedPreferences(getActivity()); + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.create_account, container, false); + username = (EditText) v.findViewById(R.id.create_account_username); + username.addTextChangedListener(mTextWatcher); + password = (EditText) v.findViewById(R.id.create_account_password); + password.addTextChangedListener(mTextWatcher); + confirmPassword = (EditText) v.findViewById(R.id.create_account_confirm_password); + confirmPassword.addTextChangedListener(mTextWatcher); + errorText = (TextView) v.findViewById(R.id.error_label); + mSettingsWarningLabel = (TextView) v.findViewById(R.id.settings_warn_label); + createButton = (Button) v.findViewById(R.id.next); + createButton.setOnClickListener(this); + serverEdit = (AutoCompleteTextView) v.findViewById(R.id.xmpp_server); + ArrayAdapter completeAdapter = ArrayAdapter.createFromResource( + getActivity(), R.array.xmpp_server_list, R.layout.simple_combobox_item); + + serverEdit.setAdapter(completeAdapter); + serverEdit.addTextChangedListener(mTextWatcher); + // show the list on second click on the text view + serverEdit.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + serverEdit.showDropDown(); + } + }); + return v; + } + + @Override + public void onStart() { + super.onStart(); + if (settings.getBoolean(BeemApplication.PROXY_USE_KEY, false)) { + mSettingsWarningLabel.setVisibility(View.VISIBLE); + } else + mSettingsWarningLabel.setVisibility(View.GONE); + } + + @Override + public void onClick(View v) { + if (v == createButton) { + boolean create = true; + if (!checkUserName()) { + username.setError(getString(R.string.create_account_err_username)); + create = false; + } + if (TextUtils.isEmpty(serverEdit.getText())) { + serverEdit.setError("Choose a server"); + create = false; + } + if (!checkPasswords()) { + password.setError(getString(R.string.create_account_err_passwords)); + confirmPassword.setError(getString(R.string.create_account_err_passwords)); + create = false; + } + if (create) { + String jid = String.format("%s@%s", username.getText(), serverEdit.getText()); + jid = StringUtils.parseBareAddress(jid); + String pass = password.getText().toString(); + task = new CreateAccountTask(); + task.execute(jid, pass); + } + } + } + + /** + * Save the user credentials. + * + * @param jid the jid of the user + * @param pass the password of the user + * + */ + private void saveCredential(String jid, String pass) { + SharedPreferences.Editor edit = settings.edit(); + edit.putString(BeemApplication.ACCOUNT_USERNAME_KEY, jid); + edit.putString(BeemApplication.ACCOUNT_PASSWORD_KEY, pass); + edit.commit(); + } + + /** + * Callback called when the account is successfully created. + * + * @param jid the jid of the account. + * @param pass the password of the account. + * + */ + private void onAccountCreationSuccess(String jid, String pass) { + Activity a = getActivity(); + saveCredential(jid, pass); + // launch login + Intent i = new Intent(a, Login.class); + i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(i); + a.finish(); + } + + /** + * Callback called when the account failed to create. + * + * @param e the exception which occurs + * + */ + private void onAccountCreationFailed(XMPPException e) { + XMPPError error = e.getXMPPError(); + if (error != null && XMPPError.Condition.conflict.equals(error.getCondition())) + errorText.setText(R.string.create_account_err_conflict); + else + errorText.setText(R.string.create_account_err_connection); + Log.v(TAG, "Unable to create an account on xmpp server", e); + } + + /** + * Check the format of the email. + * + * @return true if the email is valid. + */ + private boolean checkUserName() { + String email = username.getText().toString(); + return Pattern.matches("[a-zA-Z0-9._%+-]+", email); + } + + /** + * Check if the fields password and confirm password match. + * + * @return return true if password & confirm password fields match, else + * false + */ + private boolean checkPasswords() { + CharSequence pass = password.getText(); + return !TextUtils.isEmpty(pass) && TextUtils.equals(pass, confirmPassword.getText()); + } + + /** + * AsyncTask use to create an XMPP account on a server. + */ + private class CreateAccountTask extends AsyncTask { + + private ProgressFragment progress; + private ConnectionConfiguration config; + private XMPPException exception; + private String jid; + private String password; + private String server; + + @Override + protected void onPreExecute() { + progress = ProgressFragment.newInstance(); + progress.show(getFragmentManager(), "progressFragment"); + } + + @Override + protected void onPostExecute(Boolean result) { + if (result) { + onAccountCreationSuccess(jid, password); + } else { + onAccountCreationFailed(exception); + } + ProgressFragment pf = (ProgressFragment) getFragmentManager().findFragmentByTag("progressFragment"); + if (pf != null) + pf.dismiss(); + } + + @Override + protected Boolean doInBackground(String... params) { + Log.d(TAG, "Xmpp login task"); + jid = params[0]; + password = params[1]; + Log.d(TAG, "jid " + jid + " server " + server); + + int port = -1; + if (params.length > 2) { + server = params[2]; + } + if (params.length > 3) { + if (!TextUtils.isEmpty(params[3])) { + port = Integer.parseInt(params[3]); + } + } + Connection connection = prepareConnection(jid, server, port); + try { + connection.connect(); + AccountManager accountManager = new AccountManager(connection); + accountManager.createAccount(StringUtils.parseName(jid), password); + } catch (XMPPException e) { + Log.e(TAG, "Unable to create account", e); + exception = e; + return false; + } finally { + connection.disconnect(); + } + return true; + } + + /** + * Initialize the XMPP connection. + * + * @param jid the jid to use + * @param server the server to use (not using dns srv) may be null + * @param port the port + * + * @return the XMPPConnection prepared to connect + */ + private Connection prepareConnection(String jid, String server, int port) { + boolean useProxy = settings.getBoolean(BeemApplication.PROXY_USE_KEY, false); + ProxyInfo proxyinfo = ProxyInfo.forNoProxy(); + if (useProxy) { + String stype = settings.getString(BeemApplication.PROXY_TYPE_KEY, "HTTP"); + String phost = settings.getString(BeemApplication.PROXY_SERVER_KEY, ""); + String puser = settings.getString(BeemApplication.PROXY_USERNAME_KEY, ""); + String ppass = settings.getString(BeemApplication.PROXY_PASSWORD_KEY, ""); + int pport = Integer.parseInt(settings.getString(BeemApplication.PROXY_PORT_KEY, "1080")); + ProxyInfo.ProxyType type = ProxyType.valueOf(stype); + proxyinfo = new ProxyInfo(type, phost, pport, puser, ppass); + } + String serviceName = StringUtils.parseServer(jid); + if (port != -1 || !TextUtils.isEmpty(server)) { + if (port == -1) + port = 5222; + if (TextUtils.isEmpty(server)) + server = serviceName; + config = new ConnectionConfiguration(server, port, serviceName, proxyinfo); + } else { + config = new ConnectionConfiguration(serviceName, proxyinfo); + } + if (settings.getBoolean(BeemApplication.SMACK_DEBUG_KEY, false)) + config.setDebuggerEnabled(true); + return new XMPPConnection(config); + } + } + + /** + * A progress Fragment. + */ + public static class ProgressFragment extends DialogFragment { + + /** + * Create a new ProgressFragment. + * + * @return a ProgressFragment + */ + public static ProgressFragment newInstance() { + return new ProgressFragment(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setCancelable(false); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Log.d(TAG, "create progress dialog"); + ProgressDialog p = new ProgressDialog(getActivity()); + p.setTitle(getString(R.string.create_account_progress_title)); + p.setMessage(getString(R.string.create_account_progress_message)); + return p; + } + } + + /** + * Text watcher to test if all fields are field. + */ + private class NotEmptyTextWatcher implements TextWatcher { + + /** + * Constructor. + */ + public NotEmptyTextWatcher() { + } + + @Override + public void afterTextChanged(Editable s) { + boolean enable = !(TextUtils.isEmpty(username.getText()) + || TextUtils.isEmpty(serverEdit.getText()) + || TextUtils.isEmpty(password.getText()) + || TextUtils.isEmpty(confirmPassword.getText())); + createButton.setEnabled(enable); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/ui/wizard/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/ui/wizard/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,49 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ + +/** + * This package contains some configuration wizards. + */ +package com.beem.project.beem.ui.wizard; + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/utils/BeemBroadcastReceiver.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/utils/BeemBroadcastReceiver.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,93 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.utils; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.widget.Toast; + +import com.beem.project.beem.BeemService; +import com.beem.project.beem.R; + +/** + * Manage broadcast disconnect intent. + * @author nikita + */ +public class BeemBroadcastReceiver extends BroadcastReceiver { + + /** Broadcast intent type. */ + public static final String BEEM_CONNECTION_CLOSED = "BeemConnectionClosed"; + + /** + * constructor. + */ + public BeemBroadcastReceiver() { + } + + /** + * {@inheritDoc} + */ + @Override + public void onReceive(final Context context, final Intent intent) { + String intentAction = intent.getAction(); + if (intentAction.equals(BEEM_CONNECTION_CLOSED)) { + CharSequence message = intent.getCharSequenceExtra("message"); + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + if (context instanceof Activity) { + Activity act = (Activity) context; + act.finish(); + // The service will be unbinded in the destroy of the activity. + } + } else if (intentAction.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) { + Toast.makeText(context, context.getString(R.string.BeemBroadcastReceiverDisconnect), + Toast.LENGTH_SHORT).show(); + context.stopService(new Intent(context, BeemService.class)); + } + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/utils/BeemConnectivity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/utils/BeemConnectivity.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,117 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.utils; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.telephony.TelephonyManager; + +/** + * The Class BeemConnectivity. + */ +public final class BeemConnectivity { + + /** + * Private constructor to forbid instantiation. + */ + private BeemConnectivity() { } + + /** + * Checks if is connected. + * @param ctx the ctx + * @return true, if is connected + */ + public static boolean isConnected(final Context ctx) { + ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService( + Context.CONNECTIVITY_SERVICE); + NetworkInfo ni = cm.getActiveNetworkInfo(); + return ni != null && ni.isConnected(); + } + + /** + * Checks if is wifi. + * @param ctx the ctx + * @return true, if is wifi + */ + public static boolean isWifi(final Context ctx) { + WifiManager wm = (WifiManager) ctx.getSystemService( + Context.WIFI_SERVICE); + WifiInfo wi = wm.getConnectionInfo(); + if (wi != null + && (WifiInfo.getDetailedStateOf(wi.getSupplicantState()) + == DetailedState.OBTAINING_IPADDR + || WifiInfo.getDetailedStateOf(wi.getSupplicantState()) + == DetailedState.CONNECTED)) { + return false; + } + return false; + } + + /** + * Checks if is umts. + * @param ctx the ctx + * @return true, if is umts + */ + public static boolean isUmts(final Context ctx) { + TelephonyManager tm = (TelephonyManager) ctx.getSystemService( + Context.TELEPHONY_SERVICE); + return tm.getNetworkType() >= TelephonyManager.NETWORK_TYPE_UMTS; + } + + /** + * Checks if is edge. + * @param ctx the ctx + * @return true, if is edge + */ + public static boolean isEdge(final Context ctx) { + TelephonyManager tm = (TelephonyManager) ctx.getSystemService( + Context.TELEPHONY_SERVICE); + return tm.getNetworkType() == TelephonyManager.NETWORK_TYPE_EDGE; + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/utils/FreePort.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/utils/FreePort.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,92 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.utils; + +import java.io.IOException; +import java.net.ServerSocket; + +/** + * Utility class to get a free port. + * @author nikita + */ +public final class FreePort { + + private static final int MAGIC_10 = 10; + private static final int MAGIC_10000 = 10000; + + /** + * Private default constructor. + */ + private FreePort() { + } + + /** + * return a free port. + * @return free socket port. + */ + public static int getFreePort() { + ServerSocket ss; + int freePort = 0; + + for (int i = 0; i < MAGIC_10; i++) { + freePort = (int) (MAGIC_10000 + Math.round(Math.random() * MAGIC_10000)); + try { + ss = new ServerSocket(freePort); + freePort = ss.getLocalPort(); + ss.close(); + return freePort; + } catch (IOException e) { + e.printStackTrace(); + } + } + try { + ss = new ServerSocket(0); + freePort = ss.getLocalPort(); + ss.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return freePort; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/utils/PresenceType.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/utils/PresenceType.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,149 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.utils; + +import org.jivesoftware.smack.packet.Presence; + +/** + * Utility class to deal with Presence type. + * @author nikita + */ +public final class PresenceType { + + /** The user is available to receive messages (default). */ + public static final int AVAILABLE = 100; + + /** The user is unavailable to receive messages. */ + public static final int UNAVAILABLE = 200; + + /** Request subscription to recipient's presence. */ + + public static final int SUBSCRIBE = 300; + + /** Grant subscription to sender's presence. */ + public static final int SUBSCRIBED = 400; + + /** Request removal of subscription to sender's presence. */ + public static final int UNSUBSCRIBE = 500; + + /** Grant removal of subscription to sender's presence. */ + public static final int UNSUBSCRIBED = 600; + + /** The presence packet contains an error message. */ + public static final int ERROR = 701; + + /** + * Private default constructor. + */ + private PresenceType() { + } + + /** + * Get the presence type from a presence packet. + * @param presence the presence type + * @return an int representing the presence type + */ + public static int getPresenceType(final Presence presence) { + int res = PresenceType.ERROR; + switch (presence.getType()) { + case available: + res = PresenceType.AVAILABLE; + break; + case unavailable: + res = PresenceType.UNAVAILABLE; + break; + case subscribe: + res = PresenceType.SUBSCRIBE; + break; + case subscribed: + res = PresenceType.SUBSCRIBED; + break; + case unsubscribe: + res = PresenceType.UNSUBSCRIBE; + break; + case unsubscribed: + res = PresenceType.UNSUBSCRIBED; + break; + case error: + default: + res = PresenceType.ERROR; + } + return res; + } + + /** + * Get the smack presence mode for a status. + * @param type the status type in beem + * @return the presence mode to use in presence packet or null if there is no mode to use + */ + public static Presence.Type getPresenceTypeFrom(final int type) { + Presence.Type res; + switch (type) { + case AVAILABLE: + res = Presence.Type.available; + break; + case UNAVAILABLE: + res = Presence.Type.unavailable; + break; + case SUBSCRIBE: + res = Presence.Type.subscribe; + break; + case SUBSCRIBED: + res = Presence.Type.subscribed; + break; + case UNSUBSCRIBE: + res = Presence.Type.unsubscribe; + break; + case UNSUBSCRIBED: + res = Presence.Type.unsubscribed; + break; + case ERROR: + res = Presence.Type.error; + break; + default: + return null; + } + return res; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/utils/SortedList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/utils/SortedList.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,312 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + +*/ +package com.beem.project.beem.utils; + +import java.util.List; +import java.util.Comparator; +import java.util.ListIterator; +import java.util.Iterator; +import java.util.Collection; +import java.util.Collections; + +/** + * This class add a sort by insertion to a List. + * All methods which allow you to insert an object at a specific index + * will throw an UnsupportedOperationException. + * + * @author Da Risk + * @param the type of elements maintained by this list + */ +public class SortedList implements List { + + private final List mBackend; + private final Comparator mComparator; + + /** + * Create a SortedList. The existing elements will be sorted. + * + * @param list list to sort + * @param mComparator mComparator to use. + */ + public SortedList(final List list, final Comparator mComparator) { + this.mComparator = mComparator; + this.mBackend = list; + Collections.sort(mBackend, mComparator); + } + + @Override + public int size() { + return mBackend.size(); + } + + @Override + public boolean isEmpty() { + return mBackend.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return mBackend.contains(o); + } + + @Override + public Iterator iterator() { + return new SortedListIterator(mBackend.listIterator()); + } + + @Override + public Object[] toArray() { + return mBackend.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return mBackend.toArray(a); + } + + @Override + public boolean add(E e) { + for (ListIterator it = mBackend.listIterator(); it.hasNext();) { + if (mComparator.compare(e, it.next()) < 0) { + if (it.hasPrevious()) { + it.previous(); + } + it.add(e); + return true; + } + } + mBackend.add(e); + return true; + } + + @Override + public boolean remove(Object o) { + return mBackend.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return mBackend.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + boolean result = false; + for (E e : c) { + boolean t = add(e); + if (t) { + result = t; + } + } + return result; + } + + /** + * Add all the elements in the specified collection. + * The index param is ignored. + * + * @param index ignored + * @param c collection containing elements to be added to this list + * @return true if this list changed as a result of the call + */ + @Override + public boolean addAll(int index, Collection c) { + return addAll(c); + } + + /** + * Add all the elements in the specified collection. + * The index param is ignored. + * + * @param l collection containing elements to be added to this list + * @return true if this list changed as a result of the call + * @see addAll(Collection) + */ + public boolean addAll(SortedList l) { + if (!l.isEmpty()) { + if (mBackend.isEmpty()) { + return mBackend.addAll(l); + } + boolean result = false; + E myfirst = mBackend.get(0); + E last = l.get(l.size() - 1); + E mylast = mBackend.get(mBackend.size() - 1); + E first = l.get(0); + if (mComparator.compare(last, myfirst) < 0) { + result = mBackend.addAll(0, l); + } else if (mComparator.compare(first, mylast) > 0) { + result = mBackend.addAll(l); + } else { + Collection c = l; + result = addAll(c); + } + return result; + } + return false; + } + + @Override + public boolean removeAll(Collection c) { + return mBackend.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return mBackend.retainAll(c); + } + + @Override + public void clear() { + mBackend.clear(); + } + + @Override + public boolean equals(Object o) { + return mBackend.equals(o); + } + + @Override + public int hashCode() { + return mBackend.hashCode(); + } + + @Override + public E get(int index) { + return mBackend.get(index); + } + + @Override + public E set(int index, E element) { + throw new UnsupportedOperationException("set() is not supported in SortedList"); + } + + @Override + public void add(int index, E element) { + throw new UnsupportedOperationException("add at specific index is not supported in SortedList"); + } + + @Override + public E remove(int index) { + return mBackend.remove(index); + } + + @Override + public int indexOf(Object o) { + return mBackend.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return mBackend.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return new SortedListIterator(mBackend.listIterator()); + } + + @Override + public ListIterator listIterator(int index) { + return new SortedListIterator(mBackend.listIterator(index)); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return mBackend.subList(fromIndex, toIndex); + } + + @Override + public String toString() { + return mBackend.toString(); + } + + /** + * A SortedList.iterator don't allow list modification. + * It use the mBackend iterator for the other operations. + */ + private class SortedListIterator implements ListIterator { + + private ListIterator mIt; + + /** + * Construct SortedList.Iterator. + * + * @param iterator the iterator of the backend list + */ + SortedListIterator(final ListIterator iterator) { + mIt = iterator; + } + + @Override + public void add(E e) { + throw new UnsupportedOperationException("add() not supported in SortedList iterator"); + } + + @Override + public boolean hasNext() { + return mIt.hasNext(); + } + + @Override + public E next() { + return mIt.next(); + } + + @Override + public boolean hasPrevious() { + return mIt.hasPrevious(); + } + + @Override + public E previous() { + return mIt.previous(); + } + + @Override + public int nextIndex() { + return mIt.nextIndex(); + } + + @Override + public int previousIndex() { + return mIt.previousIndex(); + } + + @Override + public void remove() { + mIt.remove(); + } + + @Override + public void set(E e) { + throw new UnsupportedOperationException("set () not supported in SortedList iterator"); + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/utils/Status.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/utils/Status.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,186 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.utils; + +import com.beem.project.beem.R; + +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.Presence.Mode; + +/** + * Utility class to deal with status and presence value. + * @author marseille + */ +public final class Status { + + /** Status of a disconnected contact. */ + public static final int CONTACT_STATUS_DISCONNECT = 100; + + /** Status of a unavailable (long away) contact. */ + public static final int CONTACT_STATUS_UNAVAILABLE = 200; + + /** Status of a away contact. */ + public static final int CONTACT_STATUS_AWAY = 300; + + /** Status of a busy contact. */ + public static final int CONTACT_STATUS_BUSY = 400; + + /** Status of a available contact. */ + public static final int CONTACT_STATUS_AVAILABLE = 500; + + /** Status of a available for chat contact. */ + public static final int CONTACT_STATUS_AVAILABLE_FOR_CHAT = 600; + + /** + * Default constructor masked. + */ + private Status() { + } + + /** + * Get the smack presence mode for a status. + * @param status the status in beem + * @return the presence mode to use in presence packet or null if there is no mode to use + */ + public static Presence.Mode getPresenceModeFromStatus(final int status) { + Presence.Mode res; + switch (status) { + case CONTACT_STATUS_AVAILABLE: + res = Presence.Mode.available; + break; + case CONTACT_STATUS_AVAILABLE_FOR_CHAT: + res = Presence.Mode.chat; + break; + case CONTACT_STATUS_AWAY: + res = Presence.Mode.away; + break; + case CONTACT_STATUS_BUSY: + res = Presence.Mode.dnd; + break; + case CONTACT_STATUS_UNAVAILABLE: + res = Presence.Mode.xa; + break; + default: + return null; + } + return res; + } + + /** + * Get the status of from a presence packet. + * @param presence the presence containing status + * @return an int representing the status + */ + public static int getStatusFromPresence(final Presence presence) { + int res = Status.CONTACT_STATUS_DISCONNECT; + if (presence.getType().equals(Presence.Type.unavailable)) { + res = Status.CONTACT_STATUS_DISCONNECT; + } else { + Mode mode = presence.getMode(); + if (mode == null) { + res = Status.CONTACT_STATUS_AVAILABLE; + } else { + switch (mode) { + case available: + res = Status.CONTACT_STATUS_AVAILABLE; + break; + case away: + res = Status.CONTACT_STATUS_AWAY; + break; + case chat: + res = Status.CONTACT_STATUS_AVAILABLE_FOR_CHAT; + break; + case dnd: + res = Status.CONTACT_STATUS_BUSY; + break; + case xa: + res = Status.CONTACT_STATUS_UNAVAILABLE; + break; + default: + res = Status.CONTACT_STATUS_DISCONNECT; + break; + } + } + } + return res; + } + + /** + * Check if contact is online by his status. + * @param status contact status + * @return is online + */ + public static boolean statusOnline(final int status) { + return status != Status.CONTACT_STATUS_DISCONNECT; + } + + /** + * Get icon resource from status. + * @param status the status + * @return the resource icon + */ + public static int getIconBarFromStatus(final int status) { + int icon = R.drawable.beem_status_icon; + switch (status) { + case Status.CONTACT_STATUS_AVAILABLE: + icon = R.drawable.beem_status_icon_available; + break; + case Status.CONTACT_STATUS_AVAILABLE_FOR_CHAT: + icon = R.drawable.beem_status_icon_available; + break; + case Status.CONTACT_STATUS_AWAY: + icon = R.drawable.beem_status_icon_away; + break; + case Status.CONTACT_STATUS_BUSY: + icon = R.drawable.beem_status_icon_busy; + break; + case Status.CONTACT_STATUS_UNAVAILABLE: + icon = R.drawable.beem_status_icon_gray; + break; + default: + icon = R.drawable.beem_status_icon; + } + return icon; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/beem/project/beem/utils/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/beem/project/beem/utils/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,48 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +/** + * This package contains utility class to deal with various aspect of BEEM. + */ +package com.beem.project.beem.utils; + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/google/android/apps/iosched/util/LogUtils.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/google/android/apps/iosched/util/LogUtils.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,98 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.apps.iosched.util; + +import com.beem.project.beem.BuildConfig; + +import android.util.Log; + +/** + * Helper methods that make logging more consistent throughout the app. + */ +public class LogUtils { + private static final String LOG_PREFIX = "Beem"; + private static final int LOG_PREFIX_LENGTH = LOG_PREFIX.length(); + private static final int MAX_LOG_TAG_LENGTH = 23; + + public static String makeLogTag(String str) { + if (str.length() > MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH) { + return LOG_PREFIX + str.substring(0, MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH - 1); + } + + return LOG_PREFIX + str; + } + + /** + * WARNING: Don't use this when obfuscating class names with Proguard! + */ + public static String makeLogTag(Class cls) { + return makeLogTag(cls.getSimpleName()); + } + + public static void LOGD(final String tag, String message) { + if (Log.isLoggable(tag, Log.DEBUG)) { + Log.d(tag, message); + } + } + + public static void LOGD(final String tag, String message, Throwable cause) { + if (Log.isLoggable(tag, Log.DEBUG)) { + Log.d(tag, message, cause); + } + } + + public static void LOGV(final String tag, String message) { + //noinspection PointlessBooleanExpression,ConstantConditions + if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.VERBOSE)) { + Log.v(tag, message); + } + } + + public static void LOGV(final String tag, String message, Throwable cause) { + //noinspection PointlessBooleanExpression,ConstantConditions + if (BuildConfig.DEBUG && Log.isLoggable(tag, Log.VERBOSE)) { + Log.v(tag, message, cause); + } + } + + public static void LOGI(final String tag, String message) { + Log.i(tag, message); + } + + public static void LOGI(final String tag, String message, Throwable cause) { + Log.i(tag, message, cause); + } + + public static void LOGW(final String tag, String message) { + Log.w(tag, message); + } + + public static void LOGW(final String tag, String message, Throwable cause) { + Log.w(tag, message, cause); + } + + public static void LOGE(final String tag, String message) { + Log.e(tag, message); + } + + public static void LOGE(final String tag, String message, Throwable cause) { + Log.e(tag, message, cause); + } + + private LogUtils() { + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/isode/stroke/base/ByteArray.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/isode/stroke/base/ByteArray.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ +/* + * Copyright (c) 2010, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.base; + +import java.io.UnsupportedEncodingException; + +/** + * + */ +public class ByteArray { + + public ByteArray() { + } + + public ByteArray(String s) { + try { + fromBytes(s.getBytes("UTF-8")); + } catch (UnsupportedEncodingException ex) { + throw new IllegalStateException("JVM has no 'UTF-8' encoding"); + } + } + + public ByteArray(byte[] c) { + fromBytes(c); + } + + public ByteArray(ByteArray b) { + fromBytes(b.getData()); + } + + private void fromBytes(final byte[] b) { + data_ = new byte[b.length]; + System.arraycopy(b, 0, data_, 0, b.length); + } + + /*public ByteArray(char[] c, int n) { + for (int i = 0; i < n; i++) { + append(c[i]); + } + }*/ + + /** + * These are the raw, modifyable data! + * @return + */ + public byte[] getData() { + return data_; + } + + public int getSize() { + return data_.length; + } + + public boolean isEmpty() { + return getSize() == 0; + } + + /*public void resize(size_t size) { + return data_.resize(size); + }*/ + /** Immutable add */ + public static ByteArray plus(ByteArray a, ByteArray b) { + ByteArray x = new ByteArray(a.getData()); + x.append(b); + return x; + } + + /** Immutable add */ + /*public ByteArray plus(ByteArray a, char b) { + ByteArray x = new ByteArray(a.getData()); + x.append(b); + return x; + }*/ + + /** Mutable add */ + public ByteArray append(ByteArray b) { + append(b.getData()); + return this; + } + + /** Mutable add */ + private ByteArray append(byte[] b) { + int newLength = data_.length + b.length; + byte[] newData = new byte[newLength]; + for (int i = 0; i < data_.length; i++) { + newData[i] = data_[i]; + } + for (int i = 0; i < b.length; i++) { + newData[i + data_.length] = b[i]; + } + data_ = newData; + return this; + } + + /** Mutable add */ + public ByteArray append(byte b) { + byte[] bytes = {b}; + append(bytes); + return this; + } + + /** mutable add */ + public ByteArray append(String s) { + byte[] bytes; + try { + bytes = s.getBytes("UTF-8"); + } catch (UnsupportedEncodingException ex) { + throw new IllegalStateException("JVM has no 'UTF-8' encoding"); + } + append(bytes); + return this; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + (this.data_ != null ? this.data_.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object other) { + return other instanceof ByteArray && toString().equals(other.toString()); + } + + /*public char charAt(int i) { + return data_.charAt(i); + }*/ + + /*public const_iterator begin() const { + return data_.begin(); + } + + public const_iterator end() const { + return data_.end(); + }*/ + @Override + public String toString() { + try { + return new String(data_, "UTF-8"); + } catch (UnsupportedEncodingException ex) { + throw new IllegalStateException("JVM has no 'UTF-8' encoding"); + } + } + + public void readFromFile(String file) { + //FIXME: port + } + + public void clear() { + data_ = new byte[]{}; + } + private byte[] data_ = {}; + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/isode/stroke/sasl/ClientAuthenticator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/isode/stroke/sasl/ClientAuthenticator.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2010, Isode Limited, London, England. + * All rights reserved. + */ +/* + * Copyright (c) 2010, Remko Tronçon. + * All rights reserved. + */ +package com.isode.stroke.sasl; + +import com.isode.stroke.base.ByteArray; + +public abstract class ClientAuthenticator { + + public ClientAuthenticator(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setCredentials(String authcid, String password) { + setCredentials(authcid, password, ""); + } + + public void setCredentials(String authcid, String password, String authzid) { + this.authcid = authcid; + this.password = password; + this.authzid = authzid; + } + + public abstract ByteArray getResponse(); + + public abstract boolean setChallenge(ByteArray challenge); + + public String getAuthenticationID() { + return authcid; + } + + public String getAuthorizationID() { + return authzid; + } + + public String getPassword() { + return password; + } + private String name; + private String authcid; + private String password; + private String authzid; +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/isode/stroke/sasl/SCRAMSHA1ClientAuthenticator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/isode/stroke/sasl/SCRAMSHA1ClientAuthenticator.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2010, Isode Limited, London, England. + * All rights reserved. + */ +/* + * Copyright (c) 2010, Remko Tronçon. + * All rights reserved. + */ +package com.isode.stroke.sasl; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.stringcodecs.Base64; +import com.isode.stroke.stringcodecs.HMACSHA1; +import com.isode.stroke.stringcodecs.PBKDF2; +import com.isode.stroke.stringcodecs.SHA1; +import java.text.Normalizer; +import java.text.Normalizer.Form; +import java.util.HashMap; +import java.util.Map; + +public class SCRAMSHA1ClientAuthenticator extends ClientAuthenticator { + + static String escape(String s) { + String result = ""; + for (int i = 0; i < s.length(); ++i) { + if (s.charAt(i) == ',') { + result += "=2C"; + } else if (s.charAt(i) == '=') { + result += "=3D"; + } else { + result += s.charAt(i); + } + } + return result; + } + + public SCRAMSHA1ClientAuthenticator(String nonce) { + this(nonce, false); + } + public SCRAMSHA1ClientAuthenticator(String nonce, boolean useChannelBinding) { + super(useChannelBinding ? "SCRAM-SHA-1-PLUS" : "SCRAM-SHA-1"); + step = Step.Initial; + clientnonce = nonce; + this.useChannelBinding = useChannelBinding; + } + + public void setTLSChannelBindingData(ByteArray channelBindingData) { + tlsChannelBindingData = channelBindingData; + } + + public ByteArray getResponse() { + if (step.equals(Step.Initial)) { + return ByteArray.plus(getGS2Header(), getInitialBareClientMessage()); + } else if (step.equals(Step.Proof)) { + ByteArray clientKey = HMACSHA1.getResult(saltedPassword, new ByteArray("Client Key")); + ByteArray storedKey = SHA1.getHash(clientKey); + ByteArray clientSignature = HMACSHA1.getResult(storedKey, authMessage); + ByteArray clientProof = clientKey; + byte[] clientProofData = clientProof.getData(); + for (int i = 0; i < clientProofData.length; ++i) { + clientProofData[i] ^= clientSignature.getData()[i]; + } + ByteArray result = getFinalMessageWithoutProof().append(",p=").append(Base64.encode(clientProof)); + return result; + } else { + return null; + } + } + + public boolean setChallenge(ByteArray challenge) { + if (step.equals(Step.Initial)) { + if (challenge == null) { + return false; + } + initialServerMessage = challenge; + + Map keys = parseMap(initialServerMessage.toString()); + + // Extract the salt + ByteArray salt = Base64.decode(keys.get('s')); + + // Extract the server nonce + String clientServerNonce = keys.get('r'); + if (clientServerNonce.length() <= clientnonce.length()) { + return false; + } + String receivedClientNonce = clientServerNonce.substring(0, clientnonce.length()); + if (!receivedClientNonce.equals(clientnonce)) { + return false; + } + serverNonce = new ByteArray(clientServerNonce.substring(clientnonce.length())); + + + // Extract the number of iterations + int iterations = 0; + try { + iterations = Integer.parseInt(keys.get('i')); + } catch (NumberFormatException e) { + return false; + } + if (iterations <= 0) { + return false; + } + + ByteArray channelBindData = new ByteArray(); + if (useChannelBinding && tlsChannelBindingData != null) { + channelBindData = tlsChannelBindingData; + } + + // Compute all the values needed for the server signature + saltedPassword = PBKDF2.encode(new ByteArray(SASLPrep(getPassword())), salt, iterations); + authMessage = getInitialBareClientMessage().append(",").append(initialServerMessage).append(",").append(getFinalMessageWithoutProof()); + ByteArray serverKey = HMACSHA1.getResult(saltedPassword, new ByteArray("Server Key")); + serverSignature = HMACSHA1.getResult(serverKey, authMessage); + + step = Step.Proof; + return true; + } else if (step.equals(step.Proof)) { + ByteArray result = new ByteArray("v=").append(new ByteArray(Base64.encode(serverSignature))); + step = Step.Final; + return challenge != null && challenge.equals(result); + } else { + return true; + } + } + + private String SASLPrep(String source) { + return Normalizer.normalize(source, Form.NFKC); /* FIXME: Implement real SASLPrep */ + } + + private Map parseMap(String s) { + HashMap result = new HashMap(); + if (s.length() > 0) { + char key = '~'; /* initialise so it'll compile */ + String value = ""; + int i = 0; + boolean expectKey = true; + while (i < s.length()) { + if (expectKey) { + key = s.charAt(i); + expectKey = false; + i++; + } else if (s.charAt(i) == ',') { + result.put(key, value); + value = ""; + expectKey = true; + } else { + value += s.charAt(i); + } + i++; + } + result.put(key, value); + } + return result; + } + + private ByteArray getInitialBareClientMessage() { + String authenticationID = SASLPrep(getAuthenticationID()); + return new ByteArray("n=" + escape(authenticationID) + ",r=" + clientnonce); + } + + private ByteArray getGS2Header() { + + ByteArray channelBindingHeader = new ByteArray("n"); + if (tlsChannelBindingData != null) { + if (useChannelBinding) { + channelBindingHeader = new ByteArray("p=tls-unique"); + } + else { + channelBindingHeader = new ByteArray("y"); + } + } + return new ByteArray().append(channelBindingHeader).append(",").append(getAuthorizationID().isEmpty() ? new ByteArray() : new ByteArray("a=" + escape(getAuthorizationID()))).append(","); + } + + private ByteArray getFinalMessageWithoutProof() { + ByteArray channelBindData = new ByteArray(); + if (useChannelBinding && tlsChannelBindingData != null) { + channelBindData = tlsChannelBindingData; + } + return new ByteArray("c=" + Base64.encode(new ByteArray(getGS2Header()).append(channelBindData)) + ",r=" + clientnonce).append(serverNonce); + } + + private enum Step { + + Initial, + Proof, + Final + }; + private Step step; + private String clientnonce = ""; + private ByteArray initialServerMessage = new ByteArray(); + private ByteArray serverNonce = new ByteArray(); + private ByteArray authMessage = new ByteArray(); + private ByteArray saltedPassword = new ByteArray(); + private ByteArray serverSignature = new ByteArray(); + private boolean useChannelBinding; + private ByteArray tlsChannelBindingData; +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/isode/stroke/stringcodecs/Base64.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/isode/stroke/stringcodecs/Base64.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ +/* + * Copyright (c) 2010, Isode Limited, London, England. + * All rights reserved. + */ +package com.isode.stroke.stringcodecs; + +import com.isode.stroke.base.ByteArray; + +public class Base64 { + /* FIXME: Check license is ok (it is, it's BSD) */ + public static ByteArray decode(String input) { + return new ByteArray(Base64BSD.decode(input)); + } + + public static String encode(ByteArray input) { + return Base64BSD.encodeToString(input.getData(), false); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/isode/stroke/stringcodecs/Base64BSD.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/isode/stroke/stringcodecs/Base64BSD.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,575 @@ +package com.isode.stroke.stringcodecs; + +import java.util.Arrays; + +/** A very fast and memory efficient class to encode and decode to and from BASE64 in full accordance + * with RFC 2045.

+ * On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is about 10 times faster + * on small arrays (10 - 1000 bytes) and 2-3 times as fast on larger arrays (10000 - 1000000 bytes) + * compared to sun.misc.Encoder()/Decoder().

+ * + * On byte arrays the encoder is about 20% faster than Jakarta Commons Base64 Codec for encode and + * about 50% faster for decoding large arrays. This implementation is about twice as fast on very small + * arrays (< 30 bytes). If source/destination is a String this + * version is about three times as fast due to the fact that the Commons Codec result has to be recoded + * to a String from byte[], which is very expensive.

+ * + * This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only + * allocates the resulting array. This produces less garbage and it is possible to handle arrays twice + * as large as algorithms that create a temporary array. (E.g. Jakarta Commons Codec). It is unknown + * whether Sun's sun.misc.Encoder()/Decoder() produce temporary arrays but since performance + * is quite low it probably does.

+ * + * The encoder produces the same output as the Sun one except that the Sun's encoder appends + * a trailing line separator if the last character isn't a pad. Unclear why but it only adds to the + * length and is probably a side effect. Both are in conformance with RFC 2045 though.
+ * Commons codec seem to always att a trailing line separator.

+ * + * Note! + * The encode/decode method pairs (types) come in three versions with the exact same algorithm and + * thus a lot of code redundancy. This is to not create any temporary arrays for transcoding to/from different + * format types. The methods not used can simply be commented out.

+ * + * There is also a "fast" version of all decode methods that works the same way as the normal ones, but + * har a few demands on the decoded input. Normally though, these fast verions should be used if the source if + * the input is known and it hasn't bee tampered with.

+ * + * If you find the code useful or you find a bug, please send me a note at base64 @ miginfocom . com. + * + * Licence (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 2.2 + * @author Mikael Grev + * Date: 2004-aug-02 + * Time: 11:31:11 + */ + +public class Base64BSD +{ + private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); + private static final int[] IA = new int[256]; + static { + Arrays.fill(IA, -1); + for (int i = 0, iS = CA.length; i < iS; i++) + IA[CA[i]] = i; + IA['='] = 0; + } + + // **************************************************************************************** + // * char[] version + // **************************************************************************************** + + /** Encodes a raw byte array into a BASE64 char[] representation i accordance with RFC 2045. + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + public final static char[] encodeToChar(byte[] sArr, boolean lineSep) + { + // Check special case + int sLen = sArr != null ? sArr.length : 0; + if (sLen == 0) + return new char[0]; + + int eLen = (sLen / 3) * 3; // Length of even 24-bits. + int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count + int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array + char[] dArr = new char[dLen]; + + // Encode even 24-bits + for (int s = 0, d = 0, cc = 0; s < eLen;) { + // Copy next three bytes into lower 24 bits of int, paying attension to sign. + int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); + + // Encode the int into four chars + dArr[d++] = CA[(i >>> 18) & 0x3f]; + dArr[d++] = CA[(i >>> 12) & 0x3f]; + dArr[d++] = CA[(i >>> 6) & 0x3f]; + dArr[d++] = CA[i & 0x3f]; + + // Add optional line separator + if (lineSep && ++cc == 19 && d < dLen - 2) { + dArr[d++] = '\r'; + dArr[d++] = '\n'; + cc = 0; + } + } + + // Pad and encode last bits if source isn't even 24 bits. + int left = sLen - eLen; // 0 - 2. + if (left > 0) { + // Prepare the int + int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0); + + // Set last four chars + dArr[dLen - 4] = CA[i >> 12]; + dArr[dLen - 3] = CA[(i >>> 6) & 0x3f]; + dArr[dLen - 2] = left == 2 ? CA[i & 0x3f] : '='; + dArr[dLen - 1] = '='; + } + return dArr; + } + + /** Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with + * and without line separators. + * @param sArr The source array. null or length 0 will return an empty array. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + */ + public final static byte[] decode(char[] sArr) + { + // Check special case + int sLen = sArr != null ? sArr.length : 0; + if (sLen == 0) + return new byte[0]; + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. + if (IA[sArr[i]] < 0) + sepCnt++; + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) + return null; + + int pad = 0; + for (int i = sLen; i > 1 && IA[sArr[--i]] <= 0;) + if (sArr[i] == '=') + pad++; + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len;) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IA[sArr[s++]]; + if (c >= 0) + i |= c << (18 - j * 6); + else + j--; + } + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++]= (byte) (i >> 8); + if (d < len) + dArr[d++] = (byte) i; + } + } + return dArr; + } + + /** Decodes a BASE64 encoded char array that is known to be resonably well formatted. The method is about twice as + * fast as {@link #decode(char[])}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + */ + public final static byte[] decodeFast(char[] sArr) + { + // Check special case + int sLen = sArr.length; + if (sLen == 0) + return new byte[0]; + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IA[sArr[sIx]] < 0) + sIx++; + + // Trim illegal chars from end + while (eIx > 0 && IA[sArr[eIx]] < 0) + eIx--; + + // get the padding count (=) (0, 1 or 2) + int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) { + // Assemble three bytes into an int from four "valid" characters. + int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) + i |= IA[sArr[sIx++]] << (18 - j * 6); + + for (int r = 16; d < len; r -= 8) + dArr[d++] = (byte) (i >> r); + } + + return dArr; + } + + // **************************************************************************************** + // * byte[] version + // **************************************************************************************** + + /** Encodes a raw byte array into a BASE64 byte[] representation i accordance with RFC 2045. + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + public final static byte[] encodeToByte(byte[] sArr, boolean lineSep) + { + // Check special case + int sLen = sArr != null ? sArr.length : 0; + if (sLen == 0) + return new byte[0]; + + int eLen = (sLen / 3) * 3; // Length of even 24-bits. + int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count + int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array + byte[] dArr = new byte[dLen]; + + // Encode even 24-bits + for (int s = 0, d = 0, cc = 0; s < eLen;) { + // Copy next three bytes into lower 24 bits of int, paying attension to sign. + int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); + + // Encode the int into four chars + dArr[d++] = (byte) CA[(i >>> 18) & 0x3f]; + dArr[d++] = (byte) CA[(i >>> 12) & 0x3f]; + dArr[d++] = (byte) CA[(i >>> 6) & 0x3f]; + dArr[d++] = (byte) CA[i & 0x3f]; + + // Add optional line separator + if (lineSep && ++cc == 19 && d < dLen - 2) { + dArr[d++] = '\r'; + dArr[d++] = '\n'; + cc = 0; + } + } + + // Pad and encode last bits if source isn't an even 24 bits. + int left = sLen - eLen; // 0 - 2. + if (left > 0) { + // Prepare the int + int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0); + + // Set last four chars + dArr[dLen - 4] = (byte) CA[i >> 12]; + dArr[dLen - 3] = (byte) CA[(i >>> 6) & 0x3f]; + dArr[dLen - 2] = left == 2 ? (byte) CA[i & 0x3f] : (byte) '='; + dArr[dLen - 1] = '='; + } + return dArr; + } + + /** Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with + * and without line separators. + * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + */ + public final static byte[] decode(byte[] sArr) + { + // Check special case + int sLen = sArr.length; + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. + if (IA[sArr[i] & 0xff] < 0) + sepCnt++; + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) + return null; + + int pad = 0; + for (int i = sLen; i > 1 && IA[sArr[--i] & 0xff] <= 0;) + if (sArr[i] == '=') + pad++; + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len;) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IA[sArr[s++] & 0xff]; + if (c >= 0) + i |= c << (18 - j * 6); + else + j--; + } + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++]= (byte) (i >> 8); + if (d < len) + dArr[d++] = (byte) i; + } + } + + return dArr; + } + + + /** Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as + * fast as {@link #decode(byte[])}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + */ + public final static byte[] decodeFast(byte[] sArr) + { + // Check special case + int sLen = sArr.length; + if (sLen == 0) + return new byte[0]; + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0) + sIx++; + + // Trim illegal chars from end + while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0) + eIx--; + + // get the padding count (=) (0, 1 or 2) + int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) { + // Assemble three bytes into an int from four "valid" characters. + int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) + i |= IA[sArr[sIx++]] << (18 - j * 6); + + for (int r = 16; d < len; r -= 8) + dArr[d++] = (byte) (i >> r); + } + + return dArr; + } + + // **************************************************************************************** + // * String version + // **************************************************************************************** + + /** Encodes a raw byte array into a BASE64 String representation i accordance with RFC 2045. + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + public final static String encodeToString(byte[] sArr, boolean lineSep) + { + // Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower. + return new String(encodeToChar(sArr, lineSep)); + } + + /** Decodes a BASE64 encoded String. All illegal characters will be ignored and can handle both strings with + * and without line separators.
+ * Note! It can be up to about 2x the speed to call decode(str.toCharArray()) instead. That + * will create a temporary array though. This version will use str.charAt(i) to iterate the string. + * @param str The source string. null or length 0 will return an empty array. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + */ + public final static byte[] decode(String str) + { + // Check special case + int sLen = str != null ? str.length() : 0; + if (sLen == 0) + return new byte[0]; + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. + if (IA[str.charAt(i)] < 0) + sepCnt++; + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) + return null; + + // Count '=' at end + int pad = 0; + for (int i = sLen; i > 1 && IA[str.charAt(--i)] <= 0;) + if (str.charAt(i) == '=') + pad++; + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len;) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IA[str.charAt(s++)]; + if (c >= 0) + i |= c << (18 - j * 6); + else + j--; + } + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++]= (byte) (i >> 8); + if (d < len) + dArr[d++] = (byte) i; + } + } + return dArr; + } + + /** Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as + * fast as {@link #decode(String)}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * @param s The source string. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + */ + public final static byte[] decodeFast(String s) + { + // Check special case + int sLen = s.length(); + if (sLen == 0) + return new byte[0]; + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IA[s.charAt(sIx) & 0xff] < 0) + sIx++; + + // Trim illegal chars from end + while (eIx > 0 && IA[s.charAt(eIx) & 0xff] < 0) + eIx--; + + // get the padding count (=) (0, 1 or 2) + int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) { + // Assemble three bytes into an int from four "valid" characters. + int i = IA[s.charAt(sIx++)] << 18 | IA[s.charAt(sIx++)] << 12 | IA[s.charAt(sIx++)] << 6 | IA[s.charAt(sIx++)]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) + i |= IA[s.charAt(sIx++)] << (18 - j * 6); + + for (int r = 16; d < len; r -= 8) + dArr[d++] = (byte) (i >> r); + } + + return dArr; + } +} \ No newline at end of file diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/isode/stroke/stringcodecs/HMACSHA1.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/isode/stroke/stringcodecs/HMACSHA1.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010, Isode Limited, London, England. + * All rights reserved. + */ +/* + * Copyright (c) 2010, Remko Tronçon. + * All rights reserved. + */ +package com.isode.stroke.stringcodecs; + +import com.isode.stroke.base.ByteArray; + +public class HMACSHA1 { + + private static final int B = 64; + + public static ByteArray getResult(ByteArray key, ByteArray data) { + assert key.getSize() <= B; + + /* And an assert that does something */ + if (key.getSize() > B) { + throw new IllegalStateException("Invalid key size."); + } + + // Create the padded key + ByteArray paddedKey = new ByteArray(key); + for (int i = key.getSize(); i < B; ++i) { + paddedKey.append((byte) 0x0); + } + + // Create the first value + ByteArray x = new ByteArray(paddedKey); + byte[] xInner = x.getData(); + for (int i = 0; i < xInner.length; ++i) { + xInner[i] ^= 0x36; + } + x.append(data); + + // Create the second value + ByteArray y = new ByteArray(paddedKey); + byte[] yInner = y.getData(); + for (int i = 0; i < yInner.length; ++i) { + yInner[i] ^= 0x5c; + } + y.append(SHA1.getHash(x)); + + return SHA1.getHash(y); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/isode/stroke/stringcodecs/PBKDF2.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/isode/stroke/stringcodecs/PBKDF2.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010, Isode Limited, London, England. + * All rights reserved. + */ +/* + * Copyright (c) 2010, Remko Tronçon. + * All rights reserved. + */ +package com.isode.stroke.stringcodecs; + +import com.isode.stroke.base.ByteArray; + +public class PBKDF2 { + + public static ByteArray encode(ByteArray password, ByteArray salt, int iterations) { + ByteArray u = HMACSHA1.getResult(password, ByteArray.plus(salt, new ByteArray("\0\0\0\1"))); + ByteArray result = new ByteArray(u); + byte[] resultData = result.getData(); + int i = 1; + while (i < iterations) { + u = HMACSHA1.getResult(password, u); + for (int j = 0; j < u.getSize(); ++j) { + resultData[j] ^= u.getData()[j]; + } + ++i; + } + return result; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/com/isode/stroke/stringcodecs/SHA1.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/isode/stroke/stringcodecs/SHA1.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2010, Isode Limited, London, England. + * All rights reserved. + */ +/* + * Copyright (c) 2010, Remko Tronçon. + * All rights reserved. + */ +package com.isode.stroke.stringcodecs; + +import com.isode.stroke.base.ByteArray; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class SHA1 { + + public static ByteArray getHash(ByteArray data) { + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException("JRE doesn't have an SHA hash function", ex); + } + md.update(data.getData()); + return new ByteArray(md.digest()); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/de/duenndns/ssl/MTMDecision.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/de/duenndns/ssl/MTMDecision.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,33 @@ +/* MemorizingTrustManager - a TrustManager which asks the user about invalid + * certificates and memorizes their decision. + * + * Copyright (c) 2010 Georg Lukas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.duenndns.ssl; + +class MTMDecision { + public final static int DECISION_INVALID = 0; + public final static int DECISION_ABORT = 1; + public final static int DECISION_ONCE = 2; + public final static int DECISION_ALWAYS = 3; + + int state = DECISION_INVALID; +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/de/duenndns/ssl/MemorizingActivity.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/de/duenndns/ssl/MemorizingActivity.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,97 @@ +/* MemorizingTrustManager - a TrustManager which asks the user about invalid + * certificates and memorizes their decision. + * + * Copyright (c) 2010 Georg Lukas + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.duenndns.ssl; + + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.DialogInterface.*; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +import com.beem.project.beem.R; + +public class MemorizingActivity extends Activity + implements OnClickListener,OnCancelListener { + final static String TAG = "MemorizingActivity"; + + int decisionId; + String app; + + @Override + public void onCreate(Bundle savedInstanceState) { + Log.d(TAG, "onCreate"); + super.onCreate(savedInstanceState); + } + + @Override + public void onResume() { + super.onResume(); + Intent i = getIntent(); + app = i.getStringExtra(MemorizingTrustManager.DECISION_INTENT_APP); + decisionId = i.getIntExtra(MemorizingTrustManager.DECISION_INTENT_ID, MTMDecision.DECISION_INVALID); + String cert = i.getStringExtra(MemorizingTrustManager.DECISION_INTENT_CERT); + Log.d(TAG, "onResume with " + i.getExtras() + " decId=" + decisionId); + Log.d(TAG, "data: " + i.getData()); + new AlertDialog.Builder(this).setTitle(R.string.mtm_accept_cert) + .setMessage(cert) + .setPositiveButton(R.string.mtm_decision_always, this) + .setNeutralButton(R.string.mtm_decision_once, this) + .setNegativeButton(R.string.mtm_decision_abort, this) + .setOnCancelListener(this) + .create().show(); + } + + void sendDecision(int decision) { + Log.d(TAG, "Sending decision to " + app + ": " + decision); + Intent i = new Intent(MemorizingTrustManager.DECISION_INTENT + "/" + app); + i.putExtra(MemorizingTrustManager.DECISION_INTENT_ID, decisionId); + i.putExtra(MemorizingTrustManager.DECISION_INTENT_CHOICE, decision); + sendBroadcast(i); + finish(); + } + + // react on AlertDialog button press + public void onClick(DialogInterface dialog, int btnId) { + int decision; + dialog.dismiss(); + switch (btnId) { + case DialogInterface.BUTTON_POSITIVE: + decision = MTMDecision.DECISION_ALWAYS; + break; + case DialogInterface.BUTTON_NEUTRAL: + decision = MTMDecision.DECISION_ONCE; + break; + default: + decision = MTMDecision.DECISION_ABORT; + } + sendDecision(decision); + } + + public void onCancel(DialogInterface dialog) { + sendDecision(MTMDecision.DECISION_ABORT); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/de/duenndns/ssl/MemorizingTrustManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/de/duenndns/ssl/MemorizingTrustManager.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,493 @@ +/* MemorizingTrustManager - a TrustManager which asks the user about invalid + * certificates and memorizes their decision. + * + * Copyright (c) 2010 Georg Lukas + * + * MemorizingTrustManager.java contains the actual trust manager and interface + * code to create a MemorizingActivity and obtain the results. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.duenndns.ssl; + +import android.app.Activity; +import android.app.Application; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.Service; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.util.Log; +import android.os.Handler; + +import java.io.File; +import java.security.cert.*; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.MessageDigest; +import java.util.HashMap; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import com.beem.project.beem.R; + +/** + * A X509 trust manager implementation which asks the user about invalid + * certificates and memorizes their decision. + *

+ * The certificate validity is checked using the system default X509 + * TrustManager, creating a query Dialog if the check fails. + *

+ * WARNING: This only works if a dedicated thread is used for + * opening sockets! + */ +public class MemorizingTrustManager implements X509TrustManager { + final static String TAG = "MemorizingTrustManager"; + public final static String INTERCEPT_DECISION_INTENT = "de.duenndns.ssl.INTERCEPT_DECISION"; + public final static String INTERCEPT_DECISION_INTENT_LAUNCH = INTERCEPT_DECISION_INTENT + ".launch_intent"; + final static String DECISION_INTENT = "de.duenndns.ssl.DECISION"; + final static String DECISION_INTENT_APP = DECISION_INTENT + ".app"; + final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId"; + final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert"; + final static String DECISION_INTENT_CHOICE = DECISION_INTENT + ".decisionChoice"; + private final static int NOTIFICATION_ID = 100509; + + static String KEYSTORE_DIR = "KeyStore"; + static String KEYSTORE_FILE = "KeyStore.bks"; + + Context master; + Activity foregroundAct; + NotificationManager notificationManager; + private static int decisionId = 0; + private static HashMap openDecisions = new HashMap(); + + Handler masterHandler; + private File keyStoreFile; + private KeyStore appKeyStore; + private X509TrustManager defaultTrustManager; + private X509TrustManager appTrustManager; + + /** Creates an instance of the MemorizingTrustManager class. + * + * You need to supply the application context. This has to be one of: + * - Application + * - Activity + * - Service + * + * The context is used for file management, to display the dialog / + * notification and for obtaining translated strings. + * + * @param m Context for the application. + */ + public MemorizingTrustManager(Context m) { + master = m; + masterHandler = new Handler(); + notificationManager = (NotificationManager)master.getSystemService(Context.NOTIFICATION_SERVICE); + + Application app; + if (m instanceof Application) { + app = (Application)m; + } else if (m instanceof Service) { + app = ((Service)m).getApplication(); + } else if (m instanceof Activity) { + app = ((Activity)m).getApplication(); + } else throw new ClassCastException("MemorizingTrustManager context must be either Activity or Service!"); + + File dir = app.getDir(KEYSTORE_DIR, Context.MODE_PRIVATE); + keyStoreFile = new File(dir + File.separator + KEYSTORE_FILE); + + appKeyStore = loadAppKeyStore(); + defaultTrustManager = getTrustManager(null); + appTrustManager = getTrustManager(appKeyStore); + } + + /** + * Returns a X509TrustManager list containing a new instance of + * TrustManagerFactory. + * + * This function is meant for convenience only. You can use it + * as follows to integrate TrustManagerFactory for HTTPS sockets: + * + *

+	 *     SSLContext sc = SSLContext.getInstance("TLS");
+	 *     sc.init(null, MemorizingTrustManager.getInstanceList(this),
+	 *         new java.security.SecureRandom());
+	 *     HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+	 * 
+ * @param c Activity or Service to show the Dialog / Notification + */ + public static X509TrustManager[] getInstanceList(Context c) { + return new X509TrustManager[] { new MemorizingTrustManager(c) }; + } + + /** + * Binds an Activity to the MTM for displaying the query dialog. + * + * This is useful if your connection is run from a service that is + * triggered by user interaction -- in such cases the activity is + * visible and the user tends to ignore the service notification. + * + * You should never have a hidden activity bound to MTM! Use this + * function in onResume() and @see unbindDisplayActivity in onPause(). + * + * @param act Activity to be bound + */ + public void bindDisplayActivity(Activity act) { + foregroundAct = act; + } + + /** + * Removes an Activity from the MTM display stack. + * + * Always call this function when the Activity added with + * @see bindDisplayActivity is hidden. + * + * @param act Activity to be unbound + */ + public void unbindDisplayActivity(Activity act) { + // do not remove if it was overridden by a different activity + if (foregroundAct == act) + foregroundAct = null; + } + + /** + * Changes the path for the KeyStore file. + * + * The actual filename relative to the app's directory will be + * app_dirname/filename. + * + * @param dirname directory to store the KeyStore. + * @param filename file name for the KeyStore. + */ + public static void setKeyStoreFile(String dirname, String filename) { + KEYSTORE_DIR = dirname; + KEYSTORE_FILE = filename; + } + + X509TrustManager getTrustManager(KeyStore ks) { + try { + TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); + tmf.init(ks); + for (TrustManager t : tmf.getTrustManagers()) { + if (t instanceof X509TrustManager) { + return (X509TrustManager)t; + } + } + } catch (Exception e) { + // Here, we are covering up errors. It might be more useful + // however to throw them out of the constructor so the + // embedding app knows something went wrong. + Log.e(TAG, "getTrustManager(" + ks + ")", e); + } + return null; + } + + KeyStore loadAppKeyStore() { + KeyStore ks; + try { + ks = KeyStore.getInstance(KeyStore.getDefaultType()); + } catch (KeyStoreException e) { + Log.e(TAG, "getAppKeyStore()", e); + return null; + } + try { + ks.load(null, null); + ks.load(new java.io.FileInputStream(keyStoreFile), "MTM".toCharArray()); + } catch (java.io.FileNotFoundException e) { + Log.i(TAG, "getAppKeyStore(" + keyStoreFile + ") - file does not exist"); + } catch (Exception e) { + Log.e(TAG, "getAppKeyStore(" + keyStoreFile + ")", e); + } + return ks; + } + + void storeCert(X509Certificate[] chain) { + // add all certs from chain to appKeyStore + try { + for (X509Certificate c : chain) + appKeyStore.setCertificateEntry(c.getSubjectDN().toString(), c); + } catch (KeyStoreException e) { + Log.e(TAG, "storeCert(" + chain + ")", e); + return; + } + + // reload appTrustManager + appTrustManager = getTrustManager(appKeyStore); + + // store KeyStore to file + try { + java.io.FileOutputStream fos = new java.io.FileOutputStream(keyStoreFile); + appKeyStore.store(fos, "MTM".toCharArray()); + fos.close(); + } catch (Exception e) { + Log.e(TAG, "storeCert(" + keyStoreFile + ")", e); + } + } + + // if the certificate is stored in the app key store, it is considered "known" + private boolean isCertKnown(X509Certificate cert) { + try { + return appKeyStore.getCertificateAlias(cert) != null; + } catch (KeyStoreException e) { + return false; + } + } + + private boolean isExpiredException(Throwable e) { + do { + if (e instanceof CertificateExpiredException) + return true; + e = e.getCause(); + } while (e != null); + return false; + } + + public void checkCertTrusted(X509Certificate[] chain, String authType, boolean isServer) + throws CertificateException + { + Log.d(TAG, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")"); + try { + Log.d(TAG, "checkCertTrusted: trying appTrustManager"); + if (isServer) + appTrustManager.checkServerTrusted(chain, authType); + else + appTrustManager.checkClientTrusted(chain, authType); + } catch (CertificateException ae) { + // if the cert is stored in our appTrustManager, we ignore expiredness + ae.printStackTrace(); + if (isExpiredException(ae)) { + Log.i(TAG, "checkCertTrusted: accepting expired certificate from keystore"); + return; + } + if (isCertKnown(chain[0])) { + Log.i(TAG, "checkCertTrusted: accepting cert already stored in keystore"); + return; + } + try { + Log.d(TAG, "checkCertTrusted: trying defaultTrustManager"); + if (isServer) + defaultTrustManager.checkServerTrusted(chain, authType); + else + defaultTrustManager.checkClientTrusted(chain, authType); + } catch (CertificateException e) { + e.printStackTrace(); + interact(chain, authType, e); + } + } + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException + { + checkCertTrusted(chain, authType, false); + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException + { + checkCertTrusted(chain, authType, true); + } + + public X509Certificate[] getAcceptedIssuers() + { + Log.d(TAG, "getAcceptedIssuers()"); + return defaultTrustManager.getAcceptedIssuers(); + } + + private int createDecisionId(MTMDecision d) { + int myId; + synchronized(openDecisions) { + myId = decisionId; + openDecisions.put(myId, d); + decisionId += 1; + } + return myId; + } + + private static String hexString(byte[] data) { + StringBuffer si = new StringBuffer(); + for (int i = 0; i < data.length; i++) { + si.append(String.format("%02x", data[i])); + if (i < data.length - 1) + si.append(":"); + } + return si.toString(); + } + + private static String certHash(final X509Certificate cert, String digest) { + try { + MessageDigest md = MessageDigest.getInstance(digest); + md.update(cert.getEncoded()); + return hexString(md.digest()); + } catch (java.security.cert.CertificateEncodingException e) { + return e.getMessage(); + } catch (java.security.NoSuchAlgorithmException e) { + return e.getMessage(); + } + } + + private String certChainMessage(final X509Certificate[] chain, CertificateException cause) { + Throwable e = cause; + Log.d(TAG, "certChainMessage for " + e); + StringBuffer si = new StringBuffer(); + if (e.getCause() != null) { + e = e.getCause(); + si.append(e.getLocalizedMessage()); + //si.append("\n"); + } + for (X509Certificate c : chain) { + si.append("\n\n"); + si.append(c.getSubjectDN().toString()); + si.append("\nMD5: "); + si.append(certHash(c, "MD5")); + si.append("\nSHA1: "); + si.append(certHash(c, "SHA-1")); + si.append("\nSigned by: "); + si.append(c.getIssuerDN().toString()); + } + return si.toString(); + } + + void startActivityNotification(PendingIntent intent, String certName) { + Notification n = new Notification(android.R.drawable.ic_lock_lock, + master.getString(R.string.mtm_notification), + System.currentTimeMillis()); + n.setLatestEventInfo(master.getApplicationContext(), + master.getString(R.string.mtm_notification), + certName, intent); + n.flags |= Notification.FLAG_AUTO_CANCEL; + + notificationManager.notify(NOTIFICATION_ID, n); + } + + /** + * Returns the top-most entry of the activity stack. + * + * @return the Context of the currently bound UI or the master context if none is bound + */ + Context getUI() { + return (foregroundAct != null) ? foregroundAct : master; + } + + BroadcastReceiver launchServiceMode(Intent activityIntent, final String certMessage) { + BroadcastReceiver launchNotifReceiver= new BroadcastReceiver() { + public void onReceive(Context ctx, Intent i) { + Log.i(TAG, "Interception not done by the application. Send notification"); + PendingIntent pi = i.getParcelableExtra(INTERCEPT_DECISION_INTENT_LAUNCH); + startActivityNotification(pi, certMessage); + } + }; + master.registerReceiver(launchNotifReceiver, new IntentFilter(INTERCEPT_DECISION_INTENT + "/" + master.getPackageName())); + PendingIntent call = PendingIntent.getActivity(master, 0, activityIntent, 0); + Intent ni = new Intent(INTERCEPT_DECISION_INTENT + "/" + master.getPackageName()); + ni.putExtra(INTERCEPT_DECISION_INTENT_LAUNCH, call); + master.sendOrderedBroadcast(ni, null); + return launchNotifReceiver; + } + + void interact(final X509Certificate[] chain, String authType, CertificateException cause) + throws CertificateException + { + /* prepare the MTMDecision blocker object */ + MTMDecision choice = new MTMDecision(); + final int myId = createDecisionId(choice); + final String certMessage = certChainMessage(chain, cause); + BroadcastReceiver decisionReceiver = new BroadcastReceiver() { + public void onReceive(Context ctx, Intent i) { interactResult(i); } + }; + master.registerReceiver(decisionReceiver, new IntentFilter(DECISION_INTENT + "/" + master.getPackageName())); + LaunchRunnable lr = new LaunchRunnable(myId, certMessage); + masterHandler.post(lr); + + Log.d(TAG, "openDecisions: " + openDecisions); + Log.d(TAG, "waiting on " + myId); + try { + synchronized(choice) { choice.wait(); } + } catch (InterruptedException e) { + e.printStackTrace(); + } + master.unregisterReceiver(decisionReceiver); + if (lr.launchNotifReceiver != null) + master.unregisterReceiver(lr.launchNotifReceiver); + Log.d(TAG, "finished wait on " + myId + ": " + choice.state); + switch (choice.state) { + case MTMDecision.DECISION_ALWAYS: + storeCert(chain); + case MTMDecision.DECISION_ONCE: + break; + default: + throw (cause); + } + } + + public static void interactResult(Intent i) { + int decisionId = i.getIntExtra(DECISION_INTENT_ID, MTMDecision.DECISION_INVALID); + int choice = i.getIntExtra(DECISION_INTENT_CHOICE, MTMDecision.DECISION_INVALID); + Log.d(TAG, "interactResult: " + decisionId + " chose " + choice); + Log.d(TAG, "openDecisions: " + openDecisions); + + MTMDecision d; + synchronized(openDecisions) { + d = openDecisions.get(decisionId); + openDecisions.remove(decisionId); + } + if (d == null) { + Log.e(TAG, "interactResult: aborting due to stale decision reference!"); + return; + } + synchronized(d) { + d.state = choice; + d.notify(); + } + } + + private class LaunchRunnable implements Runnable { + private int myId; + private String certMessage; + BroadcastReceiver launchNotifReceiver; + + public LaunchRunnable(final int id, final String certMsg) { + myId = id; + certMessage = certMsg; + } + + public void run() { + Intent ni = new Intent(master, MemorizingActivity.class); + ni.setData(Uri.parse(MemorizingTrustManager.class.getName() + "/" + myId)); + ni.putExtra(DECISION_INTENT_APP, master.getPackageName()); + ni.putExtra(DECISION_INTENT_ID, myId); + ni.putExtra(DECISION_INTENT_CERT, certMessage); + + // we try to directly start the activity and fall back to + // making a notification + try { + getUI().startActivity(ni); + } catch (Exception e) { + Log.e(TAG, "startActivity: " + e); + launchNotifReceiver = launchServiceMode(ni, certMessage); + } + } + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/de/duenndns/ssl/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/de/duenndns/ssl/package-info.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,7 @@ +/** + * This package contains the MemorizingTrustManager library made by Georg. + * It is a "plugin" for Android Java to allow asking the user about SSL certificates + * https://github.com/ge0rg/MemorizingTrustManager + */ +package de.duenndns.ssl; + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/OtrEngine.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/OtrEngine.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,84 @@ +package net.java.otr4j; + +import java.security.PublicKey; + +import net.java.otr4j.session.SessionID; +import net.java.otr4j.session.SessionStatus; + +/** + * + * @author George Politis + * + */ +public interface OtrEngine { + + /** + * + * @param sessionID + * The session identifier. + * @param content + * The message content to be transformed. + * @return The transformed message content. + * @throws OtrException + */ + public abstract String transformReceiving(SessionID sessionID, + String content) throws OtrException; + + /** + * + * @param sessionID + * The session identifier. + * @param content + * The message content to be transformed. + * @return The transformed message content. + * @throws OtrException + */ + public abstract String transformSending(SessionID sessionID, String content) throws OtrException; + + /** + * Starts an Off-the-Record session, if there is no active one. + * + * @param sessionID + * The session identifier. + * @throws OtrException + */ + public abstract void startSession(SessionID sessionID) throws OtrException; + + /** + * Ends the Off-the-Record session, if exists. + * + * @param sessionID + * The session identifier. + * @throws OtrException + */ + public abstract void endSession(SessionID sessionID) throws OtrException; + + /** + * Stops/Starts the Off-the-Record session. + * + * @param sessionID + * The session identifier. + * @throws OtrException + */ + public abstract void refreshSession(SessionID sessionID) throws OtrException; + + /** + * + * @param sessionID + * The session identifier. + * @return The status of an Off-the-Record session. + */ + public abstract SessionStatus getSessionStatus(SessionID sessionID); + + /** + * + * @param sessionID + * The session identifier. + * @return The remote public key. + */ + public abstract PublicKey getRemotePublicKey(SessionID sessionID); + + public abstract void addOtrEngineListener(OtrEngineListener l); + + public abstract void removeOtrEngineListener(OtrEngineListener l); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/OtrEngineHost.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/OtrEngineHost.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,31 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j; + +import java.security.KeyPair; + +import net.java.otr4j.session.SessionID; + +/** + * + * This interface should be implemented by the host application. It is required + * for otr4j to work properly. + * + * @author George Politis + * + */ +public abstract interface OtrEngineHost { + public abstract void injectMessage(SessionID sessionID, String msg); + + public abstract void showWarning(SessionID sessionID, String warning); + + public abstract void showError(SessionID sessionID, String error); + + public abstract OtrPolicy getSessionPolicy(SessionID sessionID); + + public abstract KeyPair getKeyPair(SessionID sessionID); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/OtrEngineImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/OtrEngineImpl.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,114 @@ +/* + * otr4j, the open source java otr librar + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +package net.java.otr4j; + +import java.security.PublicKey; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import net.java.otr4j.session.Session; +import net.java.otr4j.session.SessionID; +import net.java.otr4j.session.SessionImpl; +import net.java.otr4j.session.SessionStatus; + +/** + * + * @author George Politis + * + */ +public class OtrEngineImpl implements OtrEngine { + + public OtrEngineImpl(OtrEngineHost host) { + if (host == null) + throw new IllegalArgumentException("OtrEgineHost is required."); + + this.setHost(host); + } + + private OtrEngineHost host; + private Map sessions; + + private Session getSession(SessionID sessionID) { + + if (sessionID == null || sessionID.equals(SessionID.Empty)) + throw new IllegalArgumentException(); + + if (sessions == null) + sessions = new Hashtable(); + + if (!sessions.containsKey(sessionID)) { + Session session = new SessionImpl(sessionID, getHost()); + sessions.put(sessionID, session); + + session.addOtrEngineListener(new OtrEngineListener() { + + public void sessionStatusChanged(SessionID sessionID) { + for (OtrEngineListener l : listeners) + l.sessionStatusChanged(sessionID); + } + }); + return session; + } else + return sessions.get(sessionID); + } + + public SessionStatus getSessionStatus(SessionID sessionID) { + return this.getSession(sessionID).getSessionStatus(); + } + + public String transformReceiving(SessionID sessionID, String msgText) + throws OtrException { + return this.getSession(sessionID).transformReceiving(msgText); + } + + public String transformSending(SessionID sessionID, String msgText) + throws OtrException { + return this.getSession(sessionID).transformSending(msgText, null); + } + + public void endSession(SessionID sessionID) throws OtrException { + this.getSession(sessionID).endSession(); + } + + public void startSession(SessionID sessionID) throws OtrException { + this.getSession(sessionID).startSession(); + } + + private void setHost(OtrEngineHost host) { + this.host = host; + } + + private OtrEngineHost getHost() { + return host; + } + + public void refreshSession(SessionID sessionID) throws OtrException { + this.getSession(sessionID).refreshSession(); + } + + public PublicKey getRemotePublicKey(SessionID sessionID) { + return this.getSession(sessionID).getRemotePublicKey(); + } + + private List listeners = new Vector(); + + public void addOtrEngineListener(OtrEngineListener l) { + synchronized (listeners) { + if (!listeners.contains(l)) + listeners.add(l); + } + } + + public void removeOtrEngineListener(OtrEngineListener l) { + synchronized (listeners) { + listeners.remove(l); + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/OtrEngineListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/OtrEngineListener.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,14 @@ +package net.java.otr4j; + +import net.java.otr4j.session.SessionID; + +/** + * This interface should be implemented by the host application. It notifies + * about session status changes. + * + * @author George Politis + * + */ +public interface OtrEngineListener { + public abstract void sessionStatusChanged(SessionID sessionID); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/OtrException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/OtrException.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,10 @@ +package net.java.otr4j; + + +public class OtrException extends Exception { + private static final long serialVersionUID = 1L; + + public OtrException(Exception e){ + super(e); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/OtrKeyManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/OtrKeyManager.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,31 @@ +package net.java.otr4j; + +import java.security.KeyPair; +import java.security.PublicKey; + +import net.java.otr4j.session.SessionID; + +public abstract interface OtrKeyManager { + + public abstract void addListener(OtrKeyManagerListener l); + + public abstract void removeListener(OtrKeyManagerListener l); + + public abstract void verify(SessionID sessionID); + + public abstract void unverify(SessionID sessionID); + + public abstract boolean isVerified(SessionID sessionID); + + public abstract String getRemoteFingerprint(SessionID sessionID); + + public abstract String getLocalFingerprint(SessionID sessionID); + + public abstract void savePublicKey(SessionID sessionID, PublicKey pubKey); + + public abstract PublicKey loadRemotePublicKey(SessionID sessionID); + + public abstract KeyPair loadLocalKeyPair(SessionID sessionID); + + public abstract void generateLocalKeyPair(SessionID sessionID); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/OtrKeyManagerImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/OtrKeyManagerImpl.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,304 @@ +package net.java.otr4j; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.List; +import java.util.Properties; +import java.util.Vector; + +import org.bouncycastle2.util.encoders.Base64; + +import net.java.otr4j.crypto.OtrCryptoEngineImpl; +import net.java.otr4j.crypto.OtrCryptoException; +import net.java.otr4j.session.SessionID; + +public class OtrKeyManagerImpl implements OtrKeyManager { + + private OtrKeyManagerStore store; + + public OtrKeyManagerImpl(OtrKeyManagerStore store) { + this.store = store; + } + + class DefaultPropertiesStore implements OtrKeyManagerStore { + private final Properties properties = new Properties(); + private String filepath; + + public DefaultPropertiesStore(String filepath) throws IOException { + if (filepath == null || filepath.length() < 1) + throw new IllegalArgumentException(); + this.filepath = filepath; + properties.clear(); + + InputStream in = new BufferedInputStream(new FileInputStream( + getConfigurationFile())); + try { + properties.load(in); + } finally { + in.close(); + } + } + + private File getConfigurationFile() throws IOException { + File configFile = new File(filepath); + if (!configFile.exists()) + configFile.createNewFile(); + return configFile; + } + + public void setProperty(String id, boolean value) { + properties.setProperty(id, "true"); + try { + this.store(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void store() throws FileNotFoundException, IOException { + OutputStream out = new FileOutputStream(getConfigurationFile()); + properties.store(out, null); + out.close(); + } + + public void setProperty(String id, byte[] value) { + properties.setProperty(id, new String(Base64.encode(value))); + try { + this.store(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void removeProperty(String id) { + properties.remove(id); + + } + + public byte[] getPropertyBytes(String id) { + String value = properties.getProperty(id); + if (value == null) + return null; + return Base64.decode(value); + } + + public boolean getPropertyBoolean(String id, boolean defaultValue) { + try { + return Boolean.valueOf(properties.get(id).toString()); + } catch (Exception e) { + return defaultValue; + } + } + } + + public OtrKeyManagerImpl(String filepath) throws IOException { + this.store = new DefaultPropertiesStore(filepath); + } + + private List listeners = new Vector(); + + public void addListener(OtrKeyManagerListener l) { + synchronized (listeners) { + if (!listeners.contains(l)) + listeners.add(l); + } + } + + public void removeListener(OtrKeyManagerListener l) { + synchronized (listeners) { + listeners.remove(l); + } + } + + public void generateLocalKeyPair(SessionID sessionID) { + if (sessionID == null) + return; + + String accountID = sessionID.getAccountID(); + KeyPair keyPair; + try { + keyPair = KeyPairGenerator.getInstance("DSA").genKeyPair(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return; + } + + // Store Public Key. + PublicKey pubKey = keyPair.getPublic(); + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pubKey + .getEncoded()); + + this.store.setProperty(accountID + ".publicKey", x509EncodedKeySpec + .getEncoded()); + + // Store Private Key. + PrivateKey privKey = keyPair.getPrivate(); + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec( + privKey.getEncoded()); + + this.store.setProperty(accountID + ".privateKey", pkcs8EncodedKeySpec + .getEncoded()); + } + + public String getLocalFingerprint(SessionID sessionID) { + KeyPair keyPair = loadLocalKeyPair(sessionID); + + if (keyPair == null) + return null; + + PublicKey pubKey = keyPair.getPublic(); + + try { + return new OtrCryptoEngineImpl().getFingerprint(pubKey); + } catch (OtrCryptoException e) { + e.printStackTrace(); + return null; + } + } + + public String getRemoteFingerprint(SessionID sessionID) { + PublicKey remotePublicKey = loadRemotePublicKey(sessionID); + if (remotePublicKey == null) + return null; + try { + return new OtrCryptoEngineImpl().getFingerprint(remotePublicKey); + } catch (OtrCryptoException e) { + e.printStackTrace(); + return null; + } + } + + public boolean isVerified(SessionID sessionID) { + if (sessionID == null) + return false; + + return this.store.getPropertyBoolean(sessionID.getUserID() + + ".publicKey.verified", false); + } + + public KeyPair loadLocalKeyPair(SessionID sessionID) { + if (sessionID == null) + return null; + + String accountID = sessionID.getAccountID(); + // Load Private Key. + byte[] b64PrivKey = this.store.getPropertyBytes(accountID + + ".privateKey"); + if (b64PrivKey == null) + return null; + + PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(b64PrivKey); + + // Load Public Key. + byte[] b64PubKey = this.store + .getPropertyBytes(accountID + ".publicKey"); + if (b64PubKey == null) + return null; + + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(b64PubKey); + + PublicKey publicKey; + PrivateKey privateKey; + + // Generate KeyPair. + KeyFactory keyFactory; + try { + keyFactory = KeyFactory.getInstance("DSA"); + publicKey = keyFactory.generatePublic(publicKeySpec); + privateKey = keyFactory.generatePrivate(privateKeySpec); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + return null; + } + + return new KeyPair(publicKey, privateKey); + } + + public PublicKey loadRemotePublicKey(SessionID sessionID) { + if (sessionID == null) + return null; + + String userID = sessionID.getUserID(); + + byte[] b64PubKey = this.store.getPropertyBytes(userID + ".publicKey"); + if (b64PubKey == null) + return null; + + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(b64PubKey); + + // Generate KeyPair. + KeyFactory keyFactory; + try { + keyFactory = KeyFactory.getInstance("DSA"); + return keyFactory.generatePublic(publicKeySpec); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + return null; + } + } + + public void savePublicKey(SessionID sessionID, PublicKey pubKey) { + if (sessionID == null) + return; + + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pubKey + .getEncoded()); + + String userID = sessionID.getUserID(); + this.store.setProperty(userID + ".publicKey", x509EncodedKeySpec + .getEncoded()); + + this.store.removeProperty(userID + ".publicKey.verified"); + } + + public void unverify(SessionID sessionID) { + if (sessionID == null) + return; + + if (!isVerified(sessionID)) + return; + + this.store + .removeProperty(sessionID.getUserID() + ".publicKey.verified"); + + for (OtrKeyManagerListener l : listeners) + l.verificationStatusChanged(sessionID); + + } + + public void verify(SessionID sessionID) { + if (sessionID == null) + return; + + if (this.isVerified(sessionID)) + return; + + this.store.setProperty(sessionID.getUserID() + ".publicKey.verified", + true); + + for (OtrKeyManagerListener l : listeners) + l.verificationStatusChanged(sessionID); + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/OtrKeyManagerListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/OtrKeyManagerListener.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,7 @@ +package net.java.otr4j; + +import net.java.otr4j.session.SessionID; + +public interface OtrKeyManagerListener { + public abstract void verificationStatusChanged(SessionID session); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/OtrKeyManagerStore.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/OtrKeyManagerStore.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,13 @@ +package net.java.otr4j; + +public interface OtrKeyManagerStore { + public abstract byte[] getPropertyBytes(String id); + + public abstract boolean getPropertyBoolean(String id, boolean defaultValue); + + public abstract void setProperty(String id, byte[] value); + + public abstract void setProperty(String id, boolean value); + + public abstract void removeProperty(String id); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/OtrPolicy.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/OtrPolicy.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,68 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j; + +/** + * + * @author George Politis + * + */ +public interface OtrPolicy { + + public static final int ALLOW_V1 = 0x01; + public static final int ALLOW_V2 = 0x02; + public static final int REQUIRE_ENCRYPTION = 0x04; + public static final int SEND_WHITESPACE_TAG = 0x08; + public static final int WHITESPACE_START_AKE = 0x10; + public static final int ERROR_START_AKE = 0x20; + public static final int VERSION_MASK = (ALLOW_V1 | ALLOW_V2); + + // The four old version 1 policies correspond to the following combinations + // of flags (adding an allowance for version 2 of the protocol): + + public static final int NEVER = 0x00; + public static final int OPPORTUNISTIC = (ALLOW_V1 | ALLOW_V2 + | SEND_WHITESPACE_TAG | WHITESPACE_START_AKE | ERROR_START_AKE); + public static final int OTRL_POLICY_MANUAL = (ALLOW_V1 | ALLOW_V2); + public static final int OTRL_POLICY_ALWAYS = (ALLOW_V1 | ALLOW_V2 + | REQUIRE_ENCRYPTION | WHITESPACE_START_AKE | ERROR_START_AKE); + public static final int OTRL_POLICY_DEFAULT = OPPORTUNISTIC; + + public abstract boolean getAllowV1(); + + public abstract boolean getAllowV2(); + + public abstract boolean getRequireEncryption(); + + public abstract boolean getSendWhitespaceTag(); + + public abstract boolean getWhitespaceStartAKE(); + + public abstract boolean getErrorStartAKE(); + + public abstract int getPolicy(); + + public abstract void setAllowV1(boolean value); + + public abstract void setAllowV2(boolean value); + + public abstract void setRequireEncryption(boolean value); + + public abstract void setSendWhitespaceTag(boolean value); + + public abstract void setWhitespaceStartAKE(boolean value); + + public abstract void setErrorStartAKE(boolean value); + + public abstract void setEnableAlways(boolean value); + + public abstract boolean getEnableAlways(); + + public abstract void setEnableManual(boolean value); + + public abstract boolean getEnableManual(); +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/OtrPolicyImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/OtrPolicyImpl.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,128 @@ +package net.java.otr4j; + +public class OtrPolicyImpl implements OtrPolicy { + + public OtrPolicyImpl() { + this.setPolicy(NEVER); + } + + public OtrPolicyImpl(int policy) { + this.setPolicy(policy); + } + + private int policy; + + public int getPolicy() { + return policy; + } + + private void setPolicy(int policy) { + this.policy = policy; + } + + public boolean getAllowV1() { + return (policy & OtrPolicy.ALLOW_V1) != 0; + } + + public boolean getAllowV2() { + return (policy & OtrPolicy.ALLOW_V2) != 0; + } + + public boolean getErrorStartAKE() { + return (policy & OtrPolicy.ERROR_START_AKE) != 0; + } + + public boolean getRequireEncryption() { + return getEnableManual() + && (policy & OtrPolicy.REQUIRE_ENCRYPTION) != 0; + } + + public boolean getSendWhitespaceTag() { + return (policy & OtrPolicy.SEND_WHITESPACE_TAG) != 0; + } + + public boolean getWhitespaceStartAKE() { + return (policy & OtrPolicy.WHITESPACE_START_AKE) != 0; + } + + public void setAllowV1(boolean value) { + if (value) + policy |= ALLOW_V1; + else + policy &= ~ALLOW_V1; + } + + public void setAllowV2(boolean value) { + if (value) + policy |= ALLOW_V2; + else + policy &= ~ALLOW_V2; + } + + public void setErrorStartAKE(boolean value) { + if (value) + policy |= ERROR_START_AKE; + else + policy &= ~ERROR_START_AKE; + } + + public void setRequireEncryption(boolean value) { + if (value) + policy |= REQUIRE_ENCRYPTION; + else + policy &= ~REQUIRE_ENCRYPTION; + } + + public void setSendWhitespaceTag(boolean value) { + if (value) + policy |= SEND_WHITESPACE_TAG; + else + policy &= ~SEND_WHITESPACE_TAG; + } + + public void setWhitespaceStartAKE(boolean value) { + if (value) + policy |= WHITESPACE_START_AKE; + else + policy &= ~WHITESPACE_START_AKE; + } + + public boolean getEnableAlways() { + return getEnableManual() && getErrorStartAKE() + && getSendWhitespaceTag() && getWhitespaceStartAKE(); + } + + public void setEnableAlways(boolean value) { + if (value) + setEnableManual(true); + + setErrorStartAKE(value); + setSendWhitespaceTag(value); + setWhitespaceStartAKE(value); + + } + + public boolean getEnableManual() { + return getAllowV1() && getAllowV2(); + } + + public void setEnableManual(boolean value) { + setAllowV1(value); + setAllowV2(value); + } + + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj == null || obj.getClass() != this.getClass()) + return false; + + OtrPolicy policy = (OtrPolicy) obj; + + return policy.getPolicy() == this.getPolicy(); + } + + public int hashCode() { + return this.getPolicy(); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/crypto/OtrCryptoEngine.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/crypto/OtrCryptoEngine.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,83 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +package net.java.otr4j.crypto; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; + +import javax.crypto.interfaces.DHPublicKey; + +/** + * + * @author George Politis + * + */ +public interface OtrCryptoEngine { + + public static final String MODULUS_TEXT = "00FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"; + public static final BigInteger MODULUS = new BigInteger(MODULUS_TEXT, 16); + public static final BigInteger BIGINTEGER_TWO = BigInteger.valueOf(2); + public static final BigInteger MODULUS_MINUS_TWO = MODULUS + .subtract(BIGINTEGER_TWO); + + public static String GENERATOR_TEXT = "2"; + public static BigInteger GENERATOR = new BigInteger(GENERATOR_TEXT, 10); + + public static final int AES_KEY_BYTE_LENGTH = 16; + public static final int SHA256_HMAC_KEY_BYTE_LENGTH = 32; + public static final int DH_PRIVATE_KEY_MINIMUM_BIT_LENGTH = 320; + public static final byte[] ZERO_CTR = new byte[] { 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00 }; + + public static final int DSA_PUB_TYPE = 0; + + public abstract KeyPair generateDHKeyPair() throws OtrCryptoException; + + public abstract DHPublicKey getDHPublicKey(byte[] mpiBytes) + throws OtrCryptoException; + + public abstract DHPublicKey getDHPublicKey(BigInteger mpi) + throws OtrCryptoException; + + public abstract byte[] sha256Hmac(byte[] b, byte[] key) + throws OtrCryptoException; + + public abstract byte[] sha256Hmac(byte[] b, byte[] key, int length) + throws OtrCryptoException; + + public abstract byte[] sha1Hmac(byte[] b, byte[] key, int length) + throws OtrCryptoException; + + public abstract byte[] sha256Hmac160(byte[] b, byte[] key) + throws OtrCryptoException; + + public abstract byte[] sha256Hash(byte[] b) throws OtrCryptoException; + + public abstract byte[] sha1Hash(byte[] b) throws OtrCryptoException; + + public abstract byte[] aesDecrypt(byte[] key, byte[] ctr, byte[] b) + throws OtrCryptoException; + + public abstract byte[] aesEncrypt(byte[] key, byte[] ctr, byte[] b) + throws OtrCryptoException; + + public abstract BigInteger generateSecret(PrivateKey privKey, + PublicKey pubKey) throws OtrCryptoException; + + public abstract byte[] sign(byte[] b, PrivateKey privatekey) + throws OtrCryptoException; + + public abstract boolean verify(byte[] b, PublicKey pubKey, byte[] rs) + throws OtrCryptoException; + + public abstract String getFingerprint(PublicKey pubKey) + throws OtrCryptoException; +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/crypto/OtrCryptoEngineImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/crypto/OtrCryptoEngineImpl.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,393 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.crypto; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; + +import javax.crypto.KeyAgreement; +import javax.crypto.interfaces.DHPrivateKey; +import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.DHPrivateKeySpec; +import javax.crypto.spec.DHPublicKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import net.java.otr4j.io.SerializationUtils; + +import org.bouncycastle2.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle2.crypto.BufferedBlockCipher; +import org.bouncycastle2.crypto.engines.AESFastEngine; +import org.bouncycastle2.crypto.generators.DHKeyPairGenerator; +import org.bouncycastle2.crypto.modes.SICBlockCipher; +import org.bouncycastle2.crypto.params.DHKeyGenerationParameters; +import org.bouncycastle2.crypto.params.DHParameters; +import org.bouncycastle2.crypto.params.DHPrivateKeyParameters; +import org.bouncycastle2.crypto.params.DHPublicKeyParameters; +import org.bouncycastle2.crypto.params.DSAParameters; +import org.bouncycastle2.crypto.params.DSAPrivateKeyParameters; +import org.bouncycastle2.crypto.params.DSAPublicKeyParameters; +import org.bouncycastle2.crypto.params.KeyParameter; +import org.bouncycastle2.crypto.params.ParametersWithIV; +import org.bouncycastle2.crypto.signers.DSASigner; +import org.bouncycastle2.util.BigIntegers; + +/** + * + * @author George Politis + * + */ +public class OtrCryptoEngineImpl implements OtrCryptoEngine { + + public KeyPair generateDHKeyPair() throws OtrCryptoException { + + // Generate a AsymmetricCipherKeyPair using BC. + DHParameters dhParams = new DHParameters(MODULUS, GENERATOR, null, + DH_PRIVATE_KEY_MINIMUM_BIT_LENGTH); + DHKeyGenerationParameters params = new DHKeyGenerationParameters( + new SecureRandom(), dhParams); + DHKeyPairGenerator kpGen = new DHKeyPairGenerator(); + + kpGen.init(params); + AsymmetricCipherKeyPair pair = kpGen.generateKeyPair(); + + // Convert this AsymmetricCipherKeyPair to a standard JCE KeyPair. + DHPublicKeyParameters pub = (DHPublicKeyParameters) pair.getPublic(); + DHPrivateKeyParameters priv = (DHPrivateKeyParameters) pair + .getPrivate(); + + try { + KeyFactory keyFac = KeyFactory.getInstance("DH"); + + DHPublicKeySpec pubKeySpecs = new DHPublicKeySpec(pub.getY(), + MODULUS, GENERATOR); + DHPublicKey pubKey = (DHPublicKey) keyFac + .generatePublic(pubKeySpecs); + + DHParameters dhParameters = priv.getParameters(); + DHPrivateKeySpec privKeySpecs = new DHPrivateKeySpec(priv.getX(), + dhParameters.getP(), dhParameters.getG()); + DHPrivateKey privKey = (DHPrivateKey) keyFac + .generatePrivate(privKeySpecs); + + return new KeyPair(pubKey, privKey); + } catch (Exception e) { + throw new OtrCryptoException(e); + } + } + + public DHPublicKey getDHPublicKey(byte[] mpiBytes) + throws OtrCryptoException { + return getDHPublicKey(new BigInteger(mpiBytes)); + } + + public DHPublicKey getDHPublicKey(BigInteger mpi) throws OtrCryptoException { + DHPublicKeySpec pubKeySpecs = new DHPublicKeySpec(mpi, MODULUS, + GENERATOR); + try { + KeyFactory keyFac = KeyFactory.getInstance("DH"); + return (DHPublicKey) keyFac.generatePublic(pubKeySpecs); + } catch (Exception e) { + throw new OtrCryptoException(e); + } + } + + public byte[] sha256Hmac(byte[] b, byte[] key) throws OtrCryptoException { + return this.sha256Hmac(b, key, 0); + } + + public byte[] sha256Hmac(byte[] b, byte[] key, int length) + throws OtrCryptoException { + + SecretKeySpec keyspec = new SecretKeySpec(key, "HmacSHA256"); + javax.crypto.Mac mac; + try { + mac = javax.crypto.Mac.getInstance("HmacSHA256"); + } catch (NoSuchAlgorithmException e) { + throw new OtrCryptoException(e); + } + try { + mac.init(keyspec); + } catch (InvalidKeyException e) { + throw new OtrCryptoException(e); + } + + byte[] macBytes = mac.doFinal(b); + + if (length > 0) { + byte[] bytes = new byte[length]; + ByteBuffer buff = ByteBuffer.wrap(macBytes); + buff.get(bytes); + return bytes; + } else { + return macBytes; + } + } + + public byte[] sha1Hmac(byte[] b, byte[] key, int length) + throws OtrCryptoException { + + try { + SecretKeySpec keyspec = new SecretKeySpec(key, "HmacSHA1"); + javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA1"); + mac.init(keyspec); + + byte[] macBytes = mac.doFinal(b); + + if (length > 0) { + byte[] bytes = new byte[length]; + ByteBuffer buff = ByteBuffer.wrap(macBytes); + buff.get(bytes); + return bytes; + } else { + return macBytes; + } + } catch (Exception e) { + throw new OtrCryptoException(e); + } + } + + public byte[] sha256Hmac160(byte[] b, byte[] key) throws OtrCryptoException { + return sha256Hmac(b, key, 20); + } + + public byte[] sha256Hash(byte[] b) throws OtrCryptoException { + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + sha256.update(b, 0, b.length); + return sha256.digest(); + } catch (Exception e) { + throw new OtrCryptoException(e); + } + } + + public byte[] sha1Hash(byte[] b) throws OtrCryptoException { + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-1"); + sha256.update(b, 0, b.length); + return sha256.digest(); + } catch (Exception e) { + throw new OtrCryptoException(e); + } + } + + public byte[] aesDecrypt(byte[] key, byte[] ctr, byte[] b) + throws OtrCryptoException { + + AESFastEngine aesDec = new AESFastEngine(); + SICBlockCipher sicAesDec = new SICBlockCipher(aesDec); + BufferedBlockCipher bufSicAesDec = new BufferedBlockCipher(sicAesDec); + + // Create initial counter value 0. + if (ctr == null) + ctr = ZERO_CTR; + bufSicAesDec.init(false, new ParametersWithIV(new KeyParameter(key), + ctr)); + byte[] aesOutLwDec = new byte[b.length]; + int done = bufSicAesDec.processBytes(b, 0, b.length, aesOutLwDec, 0); + try { + bufSicAesDec.doFinal(aesOutLwDec, done); + } catch (Exception e) { + throw new OtrCryptoException(e); + } + + return aesOutLwDec; + } + + public byte[] aesEncrypt(byte[] key, byte[] ctr, byte[] b) + throws OtrCryptoException { + + AESFastEngine aesEnc = new AESFastEngine(); + SICBlockCipher sicAesEnc = new SICBlockCipher(aesEnc); + BufferedBlockCipher bufSicAesEnc = new BufferedBlockCipher(sicAesEnc); + + // Create initial counter value 0. + if (ctr == null) + ctr = ZERO_CTR; + bufSicAesEnc.init(true, + new ParametersWithIV(new KeyParameter(key), ctr)); + byte[] aesOutLwEnc = new byte[b.length]; + int done = bufSicAesEnc.processBytes(b, 0, b.length, aesOutLwEnc, 0); + try { + bufSicAesEnc.doFinal(aesOutLwEnc, done); + } catch (Exception e) { + throw new OtrCryptoException(e); + } + return aesOutLwEnc; + } + + public BigInteger generateSecret(PrivateKey privKey, PublicKey pubKey) + throws OtrCryptoException { + try { + KeyAgreement ka = KeyAgreement.getInstance("DH"); + ka.init(privKey); + ka.doPhase(pubKey, true); + byte[] sb = ka.generateSecret(); + BigInteger s = new BigInteger(1, sb); + return s; + + } catch (Exception e) { + throw new OtrCryptoException(e); + } + } + + public byte[] sign(byte[] b, PrivateKey privatekey) + throws OtrCryptoException { + + if (!(privatekey instanceof DSAPrivateKey)) + throw new IllegalArgumentException(); + + DSAParams dsaParams = ((DSAPrivateKey) privatekey).getParams(); + DSAParameters bcDSAParameters = new DSAParameters(dsaParams.getP(), + dsaParams.getQ(), dsaParams.getG()); + + DSAPrivateKey dsaPrivateKey = (DSAPrivateKey) privatekey; + DSAPrivateKeyParameters bcDSAPrivateKeyParms = new DSAPrivateKeyParameters( + dsaPrivateKey.getX(), bcDSAParameters); + + DSASigner dsaSigner = new DSASigner(); + dsaSigner.init(true, bcDSAPrivateKeyParms); + + BigInteger q = dsaParams.getQ(); + + // Ian: Note that if you can get the standard DSA implementation you're + // using to not hash its input, you should be able to pass it ((256-bit + // value) mod q), (rather than truncating the 256-bit value) and all + // should be well. + // ref: Interop problems with libotr - DSA signature + BigInteger bmpi = new BigInteger(1, b); + BigInteger[] rs = dsaSigner.generateSignature(BigIntegers + .asUnsignedByteArray(bmpi.mod(q))); + + int siglen = q.bitLength() / 4; + int rslen = siglen / 2; + byte[] rb = BigIntegers.asUnsignedByteArray(rs[0]); + byte[] sb = BigIntegers.asUnsignedByteArray(rs[1]); + + // Create the final signature array, padded with zeros if necessary. + byte[] sig = new byte[siglen]; + Boolean writeR = false; + Boolean writeS = false; + for (int i = 0; i < siglen; i++) { + if (i < rslen) { + if (!writeR) + writeR = rb.length >= rslen - i; + sig[i] = (writeR) ? rb[i] : (byte) 0x0; + } else { + int j = i - rslen; // Rebase. + if (!writeS) + writeS = sb.length >= rslen - j; + sig[i] = (writeS) ? sb[j] : (byte) 0x0; + } + } + return sig; + } + + public boolean verify(byte[] b, PublicKey pubKey, byte[] rs) + throws OtrCryptoException { + + if (!(pubKey instanceof DSAPublicKey)) + throw new IllegalArgumentException(); + + DSAParams dsaParams = ((DSAPublicKey) pubKey).getParams(); + int qlen = dsaParams.getQ().bitLength() / 8; + ByteBuffer buff = ByteBuffer.wrap(rs); + byte[] r = new byte[qlen]; + buff.get(r); + byte[] s = new byte[qlen]; + buff.get(s); + return verify(b, pubKey, r, s); + } + + private Boolean verify(byte[] b, PublicKey pubKey, byte[] r, byte[] s) + throws OtrCryptoException { + Boolean result = verify(b, pubKey, new BigInteger(1, r), + new BigInteger(1, s)); + return result; + } + + private Boolean verify(byte[] b, PublicKey pubKey, BigInteger r, + BigInteger s) throws OtrCryptoException { + + if (!(pubKey instanceof DSAPublicKey)) + throw new IllegalArgumentException(); + + DSAParams dsaParams = ((DSAPublicKey) pubKey).getParams(); + + BigInteger q = dsaParams.getQ(); + DSAParameters bcDSAParams = new DSAParameters(dsaParams.getP(), q, + dsaParams.getG()); + + DSAPublicKey dsaPrivateKey = (DSAPublicKey) pubKey; + DSAPublicKeyParameters dsaPrivParms = new DSAPublicKeyParameters( + dsaPrivateKey.getY(), bcDSAParams); + + // Ian: Note that if you can get the standard DSA implementation you're + // using to not hash its input, you should be able to pass it ((256-bit + // value) mod q), (rather than truncating the 256-bit value) and all + // should be well. + // ref: Interop problems with libotr - DSA signature + DSASigner dsaSigner = new DSASigner(); + dsaSigner.init(false, dsaPrivParms); + + BigInteger bmpi = new BigInteger(1, b); + Boolean result = dsaSigner.verifySignature(BigIntegers + .asUnsignedByteArray(bmpi.mod(q)), r, s); + return result; + } + + public String getFingerprint(PublicKey pubKey) throws OtrCryptoException { + byte[] b; + try { + byte[] bRemotePubKey = SerializationUtils.writePublicKey(pubKey); + + if (pubKey.getAlgorithm().equals("DSA")) { + byte[] trimmed = new byte[bRemotePubKey.length - 2]; + System.arraycopy(bRemotePubKey, 2, trimmed, 0, trimmed.length); + b = new OtrCryptoEngineImpl().sha1Hash(trimmed); + } else + b = new OtrCryptoEngineImpl().sha1Hash(bRemotePubKey); + } catch (IOException e) { + throw new OtrCryptoException(e); + } + return this.byteArrayToHexString(b); + } + + private String byteArrayToHexString(byte in[]) { + byte ch = 0x00; + int i = 0; + if (in == null || in.length <= 0) + return null; + String pseudo[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", + "A", "B", "C", "D", "E", "F" }; + StringBuffer out = new StringBuffer(in.length * 2); + while (i < in.length) { + ch = (byte) (in[i] & 0xF0); + ch = (byte) (ch >>> 4); + ch = (byte) (ch & 0x0F); + out.append(pseudo[(int) ch]); + ch = (byte) (in[i] & 0x0F); + out.append(pseudo[(int) ch]); + i++; + } + + String rslt = new String(out); + return rslt; + + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/crypto/OtrCryptoException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/crypto/OtrCryptoException.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,12 @@ +package net.java.otr4j.crypto; + +import net.java.otr4j.OtrException; + +@SuppressWarnings("serial") +public class OtrCryptoException extends OtrException { + + public OtrCryptoException(Exception e) { + super(e); + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/OtrInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/OtrInputStream.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,135 @@ +package net.java.otr4j.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPublicKey; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.InvalidKeySpecException; + +import javax.crypto.interfaces.DHPublicKey; + +import net.java.otr4j.crypto.OtrCryptoEngineImpl; +import net.java.otr4j.io.messages.SignatureX; + +public class OtrInputStream extends FilterInputStream implements + SerializationConstants { + + public OtrInputStream(InputStream in) { + super(in); + } + + private int readNumber(int length) throws IOException { + byte[] b = new byte[length]; + read(b); + + int value = 0; + for (int i = 0; i < b.length; i++) { + int shift = (b.length - 1 - i) * 8; + value += (b[i] & 0x000000FF) << shift; + } + + return value; + } + + public int readByte() throws IOException { + return readNumber(TYPE_LEN_BYTE); + } + + public int readInt() throws IOException { + return readNumber(TYPE_LEN_INT); + } + + public int readShort() throws IOException { + return readNumber(TYPE_LEN_SHORT); + } + + public byte[] readCtr() throws IOException { + byte[] b = new byte[TYPE_LEN_CTR]; + read(b); + return b; + } + + public byte[] readMac() throws IOException { + byte[] b = new byte[TYPE_LEN_MAC]; + read(b); + return b; + } + + public BigInteger readBigInt() throws IOException { + byte[] b = readData(); + return new BigInteger(1, b); + } + + public byte[] readData() throws IOException { + int dataLen = readNumber(DATA_LEN); + byte[] b = new byte[dataLen]; + read(b); + return b; + } + + public PublicKey readPublicKey() throws IOException { + int type = readShort(); + switch (type) { + case 0: + BigInteger p = readBigInt(); + BigInteger q = readBigInt(); + BigInteger g = readBigInt(); + BigInteger y = readBigInt(); + DSAPublicKeySpec keySpec = new DSAPublicKeySpec(y, p, q, g); + KeyFactory keyFactory; + try { + keyFactory = KeyFactory.getInstance("DSA"); + } catch (NoSuchAlgorithmException e) { + throw new IOException(); + } + try { + return keyFactory.generatePublic(keySpec); + } catch (InvalidKeySpecException e) { + throw new IOException(); + } + default: + throw new UnsupportedOperationException(); + } + } + + public DHPublicKey readDHPublicKey() throws IOException { + BigInteger gyMpi = readBigInt(); + try { + return new OtrCryptoEngineImpl().getDHPublicKey(gyMpi); + } catch (Exception ex) { + throw new IOException(); + } + } + + public byte[] readTlvData() throws IOException { + int len = readNumber(TYPE_LEN_BYTE); + + byte[] b = new byte[len]; + in.read(b); + return b; + } + + public byte[] readSignature(PublicKey pubKey) throws IOException { + if (!pubKey.getAlgorithm().equals("DSA")) + throw new UnsupportedOperationException(); + + DSAPublicKey dsaPubKey = (DSAPublicKey) pubKey; + DSAParams dsaParams = dsaPubKey.getParams(); + byte[] sig = new byte[dsaParams.getQ().bitLength() / 4]; + read(sig); + return sig; + } + + public SignatureX readMysteriousX() throws IOException { + PublicKey pubKey = readPublicKey(); + int dhKeyID = readInt(); + byte[] sig = readSignature(pubKey); + return new SignatureX(pubKey, dhKeyID, sig); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/OtrOutputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/OtrOutputStream.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,140 @@ +package net.java.otr4j.io; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.PublicKey; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPublicKey; + +import javax.crypto.interfaces.DHPublicKey; + +import net.java.otr4j.io.messages.SignatureM; +import net.java.otr4j.io.messages.MysteriousT; +import net.java.otr4j.io.messages.SignatureX; + +import org.bouncycastle2.util.BigIntegers; + +public class OtrOutputStream extends FilterOutputStream implements + SerializationConstants { + + public OtrOutputStream(OutputStream out) { + super(out); + } + + private void writeNumber(int value, int length) throws IOException { + byte[] b = new byte[length]; + for (int i = 0; i < length; i++) { + int offset = (b.length - 1 - i) * 8; + b[i] = (byte) ((value >>> offset) & 0xFF); + } + write(b); + } + + public void writeBigInt(BigInteger bi) throws IOException { + byte[] b = BigIntegers.asUnsignedByteArray(bi); + writeData(b); + } + + public void writeByte(int b) throws IOException { + writeNumber(b, TYPE_LEN_BYTE); + } + + public void writeData(byte[] b) throws IOException { + int len = (b == null || b.length < 0) ? 0 : b.length; + writeNumber(len, DATA_LEN); + if (len > 0) + write(b); + } + + public void writeInt(int i) throws IOException { + writeNumber(i, TYPE_LEN_INT); + + } + + public void writeShort(int s) throws IOException { + writeNumber(s, TYPE_LEN_SHORT); + + } + + public void writeMac(byte[] mac) throws IOException { + if (mac == null || mac.length != TYPE_LEN_MAC) + throw new IllegalArgumentException(); + + write(mac); + } + + public void writeCtr(byte[] ctr) throws IOException { + if (ctr == null || ctr.length < 1) + return; + + int i = 0; + while (i < TYPE_LEN_CTR && i < ctr.length) { + write(ctr[i]); + i++; + } + } + + public void writeDHPublicKey(DHPublicKey dhPublicKey) throws IOException { + byte[] b = BigIntegers.asUnsignedByteArray(dhPublicKey.getY()); + writeData(b); + } + + public void writePublicKey(PublicKey pubKey) throws IOException { + if (!(pubKey instanceof DSAPublicKey)) + throw new UnsupportedOperationException( + "Key types other than DSA are not supported at the moment."); + + DSAPublicKey dsaKey = (DSAPublicKey) pubKey; + + writeShort(0); + + DSAParams dsaParams = dsaKey.getParams(); + writeBigInt(dsaParams.getP()); + writeBigInt(dsaParams.getQ()); + writeBigInt(dsaParams.getG()); + writeBigInt(dsaKey.getY()); + + } + + public void writeTlvData(byte[] b) throws IOException { + int len = (b == null || b.length < 0) ? 0 : b.length; + writeNumber(len, TLV_LEN); + if (len > 0) + write(b); + } + + public void writeSignature(byte[] signature, PublicKey pubKey) + throws IOException { + if (!pubKey.getAlgorithm().equals("DSA")) + throw new UnsupportedOperationException(); + out.write(signature); + } + + public void writeMysteriousX(SignatureX x) throws IOException { + writePublicKey(x.longTermPublicKey); + writeInt(x.dhKeyID); + writeSignature(x.signature, x.longTermPublicKey); + } + + public void writeMysteriousX(SignatureM m) throws IOException { + writeBigInt(m.localPubKey.getY()); + writeBigInt(m.remotePubKey.getY()); + writePublicKey(m.localLongTermPubKey); + writeInt(m.keyPairID); + } + + public void writeMysteriousT(MysteriousT t) throws IOException { + writeShort(t.protocolVersion); + writeByte(t.messageType); + writeByte(t.flags); + + writeInt(t.senderKeyID); + writeInt(t.recipientKeyID); + writeDHPublicKey(t.nextDH); + writeCtr(t.ctr); + writeData(t.encryptedMessage); + + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/SerializationConstants.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/SerializationConstants.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,29 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io; + +/** + * + * @author George Politis + */ +public interface SerializationConstants { + + public static final String HEAD = "?OTR"; + public static final char HEAD_ENCODED = ':'; + public static final char HEAD_ERROR = ' '; + public static final char HEAD_QUERY_Q = '?'; + public static final char HEAD_QUERY_V = 'v'; + + public static final int TYPE_LEN_BYTE = 1; + public static final int TYPE_LEN_SHORT = 2; + public static final int TYPE_LEN_INT = 4; + public static final int TYPE_LEN_MAC = 20; + public static final int TYPE_LEN_CTR = 8; + + public static final int DATA_LEN = TYPE_LEN_INT; + public static final int TLV_LEN = TYPE_LEN_SHORT; +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/SerializationUtils.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/SerializationUtils.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,341 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.math.BigInteger; +import java.security.PublicKey; +import java.util.List; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.crypto.interfaces.DHPublicKey; + +import org.bouncycastle2.util.encoders.Base64; + +import net.java.otr4j.io.messages.AbstractEncodedMessage; +import net.java.otr4j.io.messages.AbstractMessage; +import net.java.otr4j.io.messages.DHCommitMessage; +import net.java.otr4j.io.messages.DHKeyMessage; +import net.java.otr4j.io.messages.DataMessage; +import net.java.otr4j.io.messages.ErrorMessage; +import net.java.otr4j.io.messages.MysteriousT; +import net.java.otr4j.io.messages.PlainTextMessage; +import net.java.otr4j.io.messages.QueryMessage; +import net.java.otr4j.io.messages.RevealSignatureMessage; +import net.java.otr4j.io.messages.SignatureM; +import net.java.otr4j.io.messages.SignatureMessage; +import net.java.otr4j.io.messages.SignatureX; + +/** + * + * @author George Politis + */ +public class SerializationUtils { + // Mysterious X IO. + public static SignatureX toMysteriousX(byte[] b) throws IOException { + ByteArrayInputStream in = new ByteArrayInputStream(b); + OtrInputStream ois = new OtrInputStream(in); + SignatureX x = ois.readMysteriousX(); + ois.close(); + return x; + } + + public static byte[] toByteArray(SignatureX x) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OtrOutputStream oos = new OtrOutputStream(out); + oos.writeMysteriousX(x); + byte[] b = out.toByteArray(); + oos.close(); + return b; + } + + // Mysterious M IO. + public static byte[] toByteArray(SignatureM m) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OtrOutputStream oos = new OtrOutputStream(out); + oos.writeMysteriousX(m); + byte[] b = out.toByteArray(); + oos.close(); + return b; + } + + // Mysterious T IO. + public static byte[] toByteArray(MysteriousT t) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OtrOutputStream oos = new OtrOutputStream(out); + oos.writeMysteriousT(t); + byte[] b = out.toByteArray(); + oos.close(); + return b; + } + + // Basic IO. + public static byte[] writeData(byte[] b) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OtrOutputStream oos = new OtrOutputStream(out); + oos.writeData(b); + byte[] otrb = out.toByteArray(); + oos.close(); + return otrb; + } + + // BigInteger IO. + public static byte[] writeMpi(BigInteger bigInt) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OtrOutputStream oos = new OtrOutputStream(out); + oos.writeBigInt(bigInt); + byte[] b = out.toByteArray(); + oos.close(); + return b; + } + + public static BigInteger readMpi(byte[] b) throws IOException { + ByteArrayInputStream in = new ByteArrayInputStream(b); + OtrInputStream ois = new OtrInputStream(in); + BigInteger bigint = ois.readBigInt(); + ois.close(); + return bigint; + } + + // Public Key IO. + public static byte[] writePublicKey(PublicKey pubKey) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OtrOutputStream oos = new OtrOutputStream(out); + oos.writePublicKey(pubKey); + byte[] b = out.toByteArray(); + oos.close(); + return b; + } + + // Message IO. + public static String toString(AbstractMessage m) throws IOException { + StringWriter writer = new StringWriter(); + writer.write(SerializationConstants.HEAD); + + switch (m.messageType) { + case AbstractMessage.MESSAGE_ERROR: + ErrorMessage error = (ErrorMessage) m; + writer.write(SerializationConstants.HEAD_ERROR); + writer.write(error.error); + break; + case AbstractMessage.MESSAGE_PLAINTEXT: + PlainTextMessage plaintxt = (PlainTextMessage) m; + writer.write(plaintxt.cleanText); + if (plaintxt.versions != null && plaintxt.versions.size() > 0) { + writer.write(" \\t \\t\\t\\t\\t \\t \\t \\t "); + for (int version : plaintxt.versions) { + if (version == 1) + writer.write(" \\t\\t \\t "); + + if (version == 2) + writer.write(" \\t \\t \\t "); + } + } + break; + case AbstractMessage.MESSAGE_QUERY: + QueryMessage query = (QueryMessage) m; + if (query.versions.size() == 1 && query.versions.get(0) == 1) { + writer.write(SerializationConstants.HEAD_QUERY_Q); + } else { + writer.write(SerializationConstants.HEAD_QUERY_V); + for (int version : query.versions) + writer.write(String.valueOf(version)); + + writer.write(SerializationConstants.HEAD_QUERY_Q); + } + break; + case AbstractEncodedMessage.MESSAGE_DHKEY: + case AbstractEncodedMessage.MESSAGE_REVEALSIG: + case AbstractEncodedMessage.MESSAGE_SIGNATURE: + case AbstractEncodedMessage.MESSAGE_DH_COMMIT: + case AbstractEncodedMessage.MESSAGE_DATA: + ByteArrayOutputStream o = new ByteArrayOutputStream(); + OtrOutputStream s = new OtrOutputStream(o); + + switch (m.messageType) { + case AbstractEncodedMessage.MESSAGE_DHKEY: + DHKeyMessage dhkey = (DHKeyMessage) m; + s.writeShort(dhkey.protocolVersion); + s.writeByte(dhkey.messageType); + s.writeDHPublicKey(dhkey.dhPublicKey); + break; + case AbstractEncodedMessage.MESSAGE_REVEALSIG: + RevealSignatureMessage revealsig = (RevealSignatureMessage) m; + s.writeShort(revealsig.protocolVersion); + s.writeByte(revealsig.messageType); + s.writeData(revealsig.revealedKey); + s.writeData(revealsig.xEncrypted); + s.writeMac(revealsig.xEncryptedMAC); + break; + case AbstractEncodedMessage.MESSAGE_SIGNATURE: + SignatureMessage sig = (SignatureMessage) m; + s.writeShort(sig.protocolVersion); + s.writeByte(sig.messageType); + s.writeData(sig.xEncrypted); + s.writeMac(sig.xEncryptedMAC); + break; + case AbstractEncodedMessage.MESSAGE_DH_COMMIT: + DHCommitMessage dhcommit = (DHCommitMessage) m; + s.writeShort(dhcommit.protocolVersion); + s.writeByte(dhcommit.messageType); + s.writeData(dhcommit.dhPublicKeyEncrypted); + s.writeData(dhcommit.dhPublicKeyHash); + break; + case AbstractEncodedMessage.MESSAGE_DATA: + DataMessage data = (DataMessage) m; + s.writeShort(data.protocolVersion); + s.writeByte(data.messageType); + s.writeByte(data.flags); + s.writeInt(data.senderKeyID); + s.writeInt(data.recipientKeyID); + s.writeDHPublicKey(data.nextDH); + s.writeCtr(data.ctr); + s.writeData(data.encryptedMessage); + s.writeMac(data.mac); + s.writeData(data.oldMACKeys); + break; + } + + writer.write(SerializationConstants.HEAD_ENCODED); + writer.write(new String(Base64.encode(o.toByteArray()))); + writer.write("."); + break; + default: + throw new IOException("Illegal message type."); + } + + return writer.toString(); + } + + static final Pattern patternWhitespace = Pattern + .compile("( \\t \\t\\t\\t\\t \\t \\t \\t )( \\t\\t \\t )?( \\t \\t \\t )?"); + + public static AbstractMessage toMessage(String s) throws IOException { + if (s == null || s.length() <= 1) + return null; + + if (s.indexOf(SerializationConstants.HEAD) != 0 + || s.length() <= SerializationConstants.HEAD.length()) { + // Try to detect whitespace tag. + final Matcher matcher = patternWhitespace.matcher(s); + + boolean v1 = false; + boolean v2 = false; + while (matcher.find()) { + if (!v1 && matcher.start(2) > -1) + v1 = true; + + if (!v2 && matcher.start(3) > -1) + v2 = true; + + if (v1 && v2) + break; + } + + String cleanText = matcher.replaceAll(""); + List versions; + if (v1 && v2) { + versions = new Vector(2); + versions.add(0, 1); + versions.add(0, 2); + } else if (v1) { + versions = new Vector(1); + versions.add(0, 1); + } else if (v2) { + versions = new Vector(1); + versions.add(2); + } else + versions = null; + + return new PlainTextMessage(versions, cleanText); + } else { + char contentType = s.charAt(SerializationConstants.HEAD.length()); + String content = s + .substring(SerializationConstants.HEAD.length() + 1); + switch (contentType) { + case SerializationConstants.HEAD_ENCODED: + ByteArrayInputStream bin = new ByteArrayInputStream(Base64 + .decode(content.getBytes())); + OtrInputStream otr = new OtrInputStream(bin); + // We have an encoded message. + int protocolVersion = otr.readShort(); + int messageType = otr.readByte(); + switch (messageType) { + case AbstractEncodedMessage.MESSAGE_DATA: + int flags = otr.readByte(); + int senderKeyID = otr.readInt(); + int recipientKeyID = otr.readInt(); + DHPublicKey nextDH = otr.readDHPublicKey(); + byte[] ctr = otr.readCtr(); + byte[] encryptedMessage = otr.readData(); + byte[] mac = otr.readMac(); + byte[] oldMacKeys = otr.readMac(); + return new DataMessage(protocolVersion, flags, senderKeyID, + recipientKeyID, nextDH, ctr, encryptedMessage, mac, + oldMacKeys); + case AbstractEncodedMessage.MESSAGE_DH_COMMIT: + byte[] dhPublicKeyEncrypted = otr.readData(); + byte[] dhPublicKeyHash = otr.readData(); + return new DHCommitMessage(protocolVersion, + dhPublicKeyHash, dhPublicKeyEncrypted); + case AbstractEncodedMessage.MESSAGE_DHKEY: + DHPublicKey dhPublicKey = otr.readDHPublicKey(); + return new DHKeyMessage(protocolVersion, dhPublicKey); + case AbstractEncodedMessage.MESSAGE_REVEALSIG: { + byte[] revealedKey = otr.readData(); + byte[] xEncrypted = otr.readData(); + byte[] xEncryptedMac = otr.readMac(); + return new RevealSignatureMessage(protocolVersion, + xEncrypted, xEncryptedMac, revealedKey); + } + case AbstractEncodedMessage.MESSAGE_SIGNATURE: { + byte[] xEncryted = otr.readData(); + byte[] xEncryptedMac = otr.readMac(); + return new SignatureMessage(protocolVersion, xEncryted, + xEncryptedMac); + } + default: + throw new IOException("Illegal message type."); + } + case SerializationConstants.HEAD_ERROR: + return new ErrorMessage(AbstractMessage.MESSAGE_ERROR, content); + case SerializationConstants.HEAD_QUERY_V: + case SerializationConstants.HEAD_QUERY_Q: + List versions = new Vector(); + String versionString = null; + if (SerializationConstants.HEAD_QUERY_Q == contentType) { + versions.add(1); + if (content.charAt(0) == 'v') { + versionString = content.substring(1, content + .indexOf('?')); + } + } else if (SerializationConstants.HEAD_QUERY_V == contentType) { + versionString = content.substring(0, content.indexOf('?')); + } + + if (versionString != null) { + StringReader sr = new StringReader(versionString); + int c; + while ((c = sr.read()) != -1) + if (!versions.contains(c)) + versions.add(Integer.parseInt(String + .valueOf((char) c))); + } + QueryMessage query = new QueryMessage(versions); + return query; + default: + throw new IOException("Uknown message type."); + } + } + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/messages/AbstractEncodedMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/messages/AbstractEncodedMessage.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,52 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io.messages; + +/** + * + * @author George Politis + */ +public abstract class AbstractEncodedMessage extends AbstractMessage { + // Fields. + public int protocolVersion; + + // Ctor. + public AbstractEncodedMessage(int messageType, int protocolVersion) { + super(messageType); + this.protocolVersion = protocolVersion; + } + + // Methods. + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + protocolVersion; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + AbstractEncodedMessage other = (AbstractEncodedMessage) obj; + if (protocolVersion != other.protocolVersion) + return false; + return true; + } + + // Encoded Message Types + public static final int MESSAGE_DH_COMMIT = 0x02; + public static final int MESSAGE_DATA = 0x03; + public static final int MESSAGE_DHKEY = 0x0a; + public static final int MESSAGE_REVEALSIG = 0x11; + public static final int MESSAGE_SIGNATURE = 0x12; +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/messages/AbstractMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/messages/AbstractMessage.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,49 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io.messages; + +/** + * + * @author George Politis + */ +public abstract class AbstractMessage { + // Fields. + public int messageType; + + // Ctor. + public AbstractMessage(int messageType) { + this.messageType = messageType; + } + + // Methods. + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + messageType; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AbstractMessage other = (AbstractMessage) obj; + if (messageType != other.messageType) + return false; + return true; + } + + // Unencoded + public static final int MESSAGE_ERROR = 0xff; + public static final int MESSAGE_QUERY = 0x100; + public static final int MESSAGE_PLAINTEXT = 0x102; +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/messages/DHCommitMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/messages/DHCommitMessage.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,55 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io.messages; + +import java.util.Arrays; + +/** + * + * @author George Politis + */ +public class DHCommitMessage extends AbstractEncodedMessage { + + // Fields. + public byte[] dhPublicKeyEncrypted; + public byte[] dhPublicKeyHash; + + // Ctor. + public DHCommitMessage(int protocolVersion, byte[] dhPublicKeyHash, + byte[] dhPublicKeyEncrypted) { + super(MESSAGE_DH_COMMIT, protocolVersion); + this.dhPublicKeyEncrypted = dhPublicKeyEncrypted; + this.dhPublicKeyHash = dhPublicKeyHash; + } + + // Methods. + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Arrays.hashCode(dhPublicKeyEncrypted); + result = prime * result + Arrays.hashCode(dhPublicKeyHash); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + DHCommitMessage other = (DHCommitMessage) obj; + if (!Arrays.equals(dhPublicKeyEncrypted, other.dhPublicKeyEncrypted)) + return false; + if (!Arrays.equals(dhPublicKeyHash, other.dhPublicKeyHash)) + return false; + return true; + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/messages/DHKeyMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/messages/DHKeyMessage.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,53 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io.messages; + +import javax.crypto.interfaces.DHPublicKey; + +/** + * + * @author George Politis + */ +public class DHKeyMessage extends AbstractEncodedMessage { + + // Fields. + public DHPublicKey dhPublicKey; + + // Ctor. + public DHKeyMessage(int protocolVersion, DHPublicKey dhPublicKey) { + super(MESSAGE_DHKEY, protocolVersion); + this.dhPublicKey = dhPublicKey; + } + + // Methods. + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + // TODO: Needs work. + result = prime * result + + ((dhPublicKey == null) ? 0 : dhPublicKey.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + DHKeyMessage other = (DHKeyMessage) obj; + if (dhPublicKey == null) { + if (other.dhPublicKey != null) + return false; + } else if (dhPublicKey.getY().compareTo(other.dhPublicKey.getY()) != 0) + return false; + return true; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/messages/DataMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/messages/DataMessage.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,103 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io.messages; + +import java.util.Arrays; + +import javax.crypto.interfaces.DHPublicKey; + +/** + * + * @author George Politis + */ +public class DataMessage extends AbstractEncodedMessage { + + // Fields. + public byte[] mac; + public byte[] oldMACKeys; + + public int flags; + public int senderKeyID; + public int recipientKeyID; + public DHPublicKey nextDH; + public byte[] ctr; + public byte[] encryptedMessage; + + // Ctor. + public DataMessage(int protocolVersion, int flags, int senderKeyID, + int recipientKeyID, DHPublicKey nextDH, byte[] ctr, + byte[] encryptedMessage, byte[] mac, byte[] oldMacKeys) { + super(MESSAGE_DATA, protocolVersion); + + this.flags = flags; + this.senderKeyID = senderKeyID; + this.recipientKeyID = recipientKeyID; + this.nextDH = nextDH; + this.ctr = ctr; + this.encryptedMessage = encryptedMessage; + this.mac = mac; + this.oldMACKeys = oldMacKeys; + } + + public DataMessage(MysteriousT t, byte[] mac, byte[] oldMacKeys) { + this(t.protocolVersion, t.flags, t.senderKeyID, t.recipientKeyID, + t.nextDH, t.ctr, t.encryptedMessage, mac, oldMacKeys); + } + + // Methods. + public MysteriousT getT() { + return new MysteriousT(protocolVersion, flags, senderKeyID, + recipientKeyID, nextDH, ctr, encryptedMessage); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Arrays.hashCode(ctr); + result = prime * result + Arrays.hashCode(encryptedMessage); + result = prime * result + flags; + result = prime * result + Arrays.hashCode(mac); + // TODO: Needs work. + result = prime * result + ((nextDH == null) ? 0 : nextDH.hashCode()); + result = prime * result + Arrays.hashCode(oldMACKeys); + result = prime * result + recipientKeyID; + result = prime * result + senderKeyID; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + DataMessage other = (DataMessage) obj; + if (!Arrays.equals(ctr, other.ctr)) + return false; + if (!Arrays.equals(encryptedMessage, other.encryptedMessage)) + return false; + if (flags != other.flags) + return false; + if (!Arrays.equals(mac, other.mac)) + return false; + if (nextDH == null) { + if (other.nextDH != null) + return false; + } else if (!nextDH.equals(other.nextDH)) + return false; + if (!Arrays.equals(oldMACKeys, other.oldMACKeys)) + return false; + if (recipientKeyID != other.recipientKeyID) + return false; + if (senderKeyID != other.senderKeyID) + return false; + return true; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/messages/ErrorMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/messages/ErrorMessage.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,48 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io.messages; + +/** + * + * @author George Politis + */ +public class ErrorMessage extends AbstractMessage { + // Fields. + public String error; + + // Ctor. + public ErrorMessage(int messageType, String error) { + super(messageType); + this.error = error; + } + + // Methods. + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((error == null) ? 0 : error.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + ErrorMessage other = (ErrorMessage) obj; + if (error == null) { + if (other.error != null) + return false; + } else if (!error.equals(other.error)) + return false; + return true; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/messages/MysteriousT.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/messages/MysteriousT.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,82 @@ +package net.java.otr4j.io.messages; + +import java.util.Arrays; + +import javax.crypto.interfaces.DHPublicKey; + +public class MysteriousT { + // Fields. + public int protocolVersion; + public int messageType; + public int flags; + public int senderKeyID; + public int recipientKeyID; + public DHPublicKey nextDH; + public byte[] ctr; + public byte[] encryptedMessage; + + // Ctor. + public MysteriousT(int protocolVersion, int flags, int senderKeyID, + int recipientKeyID, DHPublicKey nextDH, byte[] ctr, + byte[] encryptedMessage) { + + this.protocolVersion = protocolVersion; + this.messageType = AbstractEncodedMessage.MESSAGE_DATA; + this.flags = flags; + this.senderKeyID = senderKeyID; + this.recipientKeyID = recipientKeyID; + this.nextDH = nextDH; + this.ctr = ctr; + this.encryptedMessage = encryptedMessage; + } + + // Methods. + @Override + public int hashCode() { + // TODO: Needs work. + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(ctr); + result = prime * result + Arrays.hashCode(encryptedMessage); + result = prime * result + flags; + result = prime * result + messageType; + result = prime * result + ((nextDH == null) ? 0 : nextDH.hashCode()); + result = prime * result + protocolVersion; + result = prime * result + recipientKeyID; + result = prime * result + senderKeyID; + return result; + } + + @Override + public boolean equals(Object obj) { + // TODO: Needs work. + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + MysteriousT other = (MysteriousT) obj; + if (!Arrays.equals(ctr, other.ctr)) + return false; + if (!Arrays.equals(encryptedMessage, other.encryptedMessage)) + return false; + if (flags != other.flags) + return false; + if (messageType != other.messageType) + return false; + if (nextDH == null) { + if (other.nextDH != null) + return false; + } else if (!nextDH.equals(other.nextDH)) + return false; + if (protocolVersion != other.protocolVersion) + return false; + if (recipientKeyID != other.recipientKeyID) + return false; + if (senderKeyID != other.senderKeyID) + return false; + return true; + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/messages/PlainTextMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/messages/PlainTextMessage.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,52 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io.messages; + +import java.util.List; + +/** + * + * @author George Politis + */ +public class PlainTextMessage extends QueryMessage { + // Fields. + public String cleanText; + + // Ctor. + public PlainTextMessage(List versions, String cleanText) { + super(MESSAGE_PLAINTEXT, versions); + this.cleanText = cleanText; + } + + // Methods. + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + + ((cleanText == null) ? 0 : cleanText.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + PlainTextMessage other = (PlainTextMessage) obj; + if (cleanText == null) { + if (other.cleanText != null) + return false; + } else if (!cleanText.equals(other.cleanText)) + return false; + return true; + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/messages/QueryMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/messages/QueryMessage.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,56 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io.messages; + +import java.util.List; + +/** + * + * @author George Politis + */ +public class QueryMessage extends AbstractMessage { + // Fields. + public List versions; + + // Ctor. + protected QueryMessage(int messageType, List versions) { + super(messageType); + this.versions = versions; + } + + public QueryMessage(List versions) { + this(MESSAGE_QUERY, versions); + } + + // Methods. + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + + ((versions == null) ? 0 : versions.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + QueryMessage other = (QueryMessage) obj; + if (versions == null) { + if (other.versions != null) + return false; + } else if (!versions.equals(other.versions)) + return false; + return true; + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/messages/RevealSignatureMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/messages/RevealSignatureMessage.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,49 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io.messages; + +import java.util.Arrays; + +/** + * + * @author George Politis + */ +public class RevealSignatureMessage extends SignatureMessage { + // Fields. + public byte[] revealedKey; + + // Ctor. + public RevealSignatureMessage(int protocolVersion, byte[] xEncrypted, + byte[] xEncryptedMAC, byte[] revealedKey) { + super(MESSAGE_REVEALSIG, protocolVersion, xEncrypted, xEncryptedMAC); + + this.revealedKey = revealedKey; + } + + // Methods. + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Arrays.hashCode(revealedKey); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + RevealSignatureMessage other = (RevealSignatureMessage) obj; + if (!Arrays.equals(revealedKey, other.revealedKey)) + return false; + return true; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/messages/SignatureM.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/messages/SignatureM.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,82 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io.messages; + +import java.security.PublicKey; + +import javax.crypto.interfaces.DHPublicKey; + +/** + * + * @author George Politis + */ +public class SignatureM { + // Fields. + public DHPublicKey localPubKey; + public DHPublicKey remotePubKey; + public PublicKey localLongTermPubKey; + public int keyPairID; + + // Ctor. + public SignatureM(DHPublicKey localPubKey, DHPublicKey remotePublicKey, + PublicKey localLongTermPublicKey, int keyPairID) { + + this.localPubKey = localPubKey; + this.remotePubKey = remotePublicKey; + this.localLongTermPubKey = localLongTermPublicKey; + this.keyPairID = keyPairID; + } + + // Methods. + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + keyPairID; + // TODO: Needs work. + result = prime + * result + + ((localLongTermPubKey == null) ? 0 : localLongTermPubKey + .hashCode()); + result = prime * result + + ((localPubKey == null) ? 0 : localPubKey.hashCode()); + result = prime * result + + ((remotePubKey == null) ? 0 : remotePubKey.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + // TODO: Needs work. + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SignatureM other = (SignatureM) obj; + if (keyPairID != other.keyPairID) + return false; + if (localLongTermPubKey == null) { + if (other.localLongTermPubKey != null) + return false; + } else if (!localLongTermPubKey.equals(other.localLongTermPubKey)) + return false; + if (localPubKey == null) { + if (other.localPubKey != null) + return false; + } else if (!localPubKey.equals(other.localPubKey)) + return false; + if (remotePubKey == null) { + if (other.remotePubKey != null) + return false; + } else if (!remotePubKey.equals(other.remotePubKey)) + return false; + return true; + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/messages/SignatureMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/messages/SignatureMessage.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,82 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io.messages; + +import java.io.IOException; +import java.util.Arrays; + +import net.java.otr4j.OtrException; +import net.java.otr4j.crypto.OtrCryptoEngineImpl; +import net.java.otr4j.io.SerializationUtils; + +/** + * + * @author George Politis + */ +public class SignatureMessage extends AbstractEncodedMessage { + // Fields. + public byte[] xEncrypted; + public byte[] xEncryptedMAC; + + // Ctor. + protected SignatureMessage(int messageType, int protocolVersion, + byte[] xEncrypted, byte[] xEncryptedMAC) { + super(messageType, protocolVersion); + this.xEncrypted = xEncrypted; + this.xEncryptedMAC = xEncryptedMAC; + } + + public SignatureMessage(int protocolVersion, byte[] xEncrypted, + byte[] xEncryptedMAC) { + this(MESSAGE_SIGNATURE, protocolVersion, xEncrypted, xEncryptedMAC); + } + + // Memthods. + public byte[] decrypt(byte[] key) throws OtrException { + return new OtrCryptoEngineImpl().aesDecrypt(key, null, xEncrypted); + } + + public boolean verify(byte[] key) throws OtrException { + // Hash the key. + byte[] xbEncrypted; + try { + xbEncrypted = SerializationUtils.writeData(xEncrypted); + } catch (IOException e) { + throw new OtrException(e); + } + + byte[] xEncryptedMAC = new OtrCryptoEngineImpl().sha256Hmac160( + xbEncrypted, key); + // Verify signature. + return Arrays.equals(xEncryptedMAC, xEncryptedMAC); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Arrays.hashCode(xEncrypted); + result = prime * result + Arrays.hashCode(xEncryptedMAC); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + SignatureMessage other = (SignatureMessage) obj; + if (!Arrays.equals(xEncrypted, other.xEncrypted)) + return false; + if (!Arrays.equals(xEncryptedMAC, other.xEncryptedMAC)) + return false; + return true; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/io/messages/SignatureX.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/io/messages/SignatureX.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,67 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io.messages; + +import java.security.PublicKey; +import java.util.Arrays; + +/** + * + * @author George Politis + */ +public class SignatureX { + // Fields. + public PublicKey longTermPublicKey; + public int dhKeyID; + public byte[] signature; + + // Ctor. + public SignatureX(PublicKey ourLongTermPublicKey, int ourKeyID, + byte[] signature) { + this.longTermPublicKey = ourLongTermPublicKey; + this.dhKeyID = ourKeyID; + this.signature = signature; + } + + // Methods. + @Override + public int hashCode() { + // TODO: Needs work. + final int prime = 31; + int result = 1; + result = prime * result + dhKeyID; + result = prime + * result + + ((longTermPublicKey == null) ? 0 : longTermPublicKey + .hashCode()); + result = prime * result + Arrays.hashCode(signature); + return result; + } + + @Override + public boolean equals(Object obj) { + // TODO: Needs work. + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SignatureX other = (SignatureX) obj; + if (dhKeyID != other.dhKeyID) + return false; + if (longTermPublicKey == null) { + if (other.longTermPublicKey != null) + return false; + } else if (!longTermPublicKey.equals(other.longTermPublicKey)) + return false; + if (!Arrays.equals(signature, other.signature)) + return false; + return true; + } + +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/session/AuthContext.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/session/AuthContext.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,55 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.session; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.PublicKey; + +import javax.crypto.interfaces.DHPublicKey; + +import net.java.otr4j.OtrException; +import net.java.otr4j.io.messages.AbstractMessage; + +/** + * + * @author George Politis + */ +interface AuthContext { + + public static final int NONE = 0; + public static final int AWAITING_DHKEY = 1; + public static final int AWAITING_REVEALSIG = 2; + public static final int AWAITING_SIG = 3; + public static final int V1_SETUP = 4; + public static final byte C_START = (byte) 0x01; + public static final byte M1_START = (byte) 0x02; + public static final byte M2_START = (byte) 0x03; + public static final byte M1p_START = (byte) 0x04; + public static final byte M2p_START = (byte) 0x05; + + public abstract void reset(); + + public abstract boolean getIsSecure(); + + public abstract DHPublicKey getRemoteDHPublicKey(); + + public abstract KeyPair getLocalDHKeyPair() throws OtrException; + + public abstract BigInteger getS() throws OtrException; + + public abstract void handleReceivingMessage(AbstractMessage m) + throws OtrException; + + public abstract void startV2Auth() throws OtrException; + + public abstract void respondV2Auth() throws OtrException; + + public abstract PublicKey getRemoteLongTermPublicKey(); + + public abstract KeyPair getLocalLongTermKeyPair(); +} \ No newline at end of file diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/session/AuthContextImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/session/AuthContextImpl.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,766 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.session; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.KeyPair; +import java.security.PublicKey; +import java.util.Arrays; +import java.util.Random; +import java.util.Vector; +import java.util.logging.Logger; + +import javax.crypto.interfaces.DHPublicKey; + +import net.java.otr4j.OtrException; +import net.java.otr4j.crypto.OtrCryptoEngine; +import net.java.otr4j.crypto.OtrCryptoEngineImpl; +import net.java.otr4j.io.SerializationUtils; +import net.java.otr4j.io.messages.DHCommitMessage; +import net.java.otr4j.io.messages.DHKeyMessage; +import net.java.otr4j.io.messages.AbstractEncodedMessage; +import net.java.otr4j.io.messages.AbstractMessage; +import net.java.otr4j.io.messages.SignatureM; +import net.java.otr4j.io.messages.SignatureX; +import net.java.otr4j.io.messages.QueryMessage; +import net.java.otr4j.io.messages.RevealSignatureMessage; +import net.java.otr4j.io.messages.SignatureMessage; + +/** + * + * @author George Politis + */ +class AuthContextImpl implements AuthContext { + + public AuthContextImpl(Session session) { + this.setSession(session); + this.reset(); + } + + private Session session; + + private int authenticationState; + private byte[] r; + + private DHPublicKey remoteDHPublicKey; + private byte[] remoteDHPublicKeyEncrypted; + private byte[] remoteDHPublicKeyHash; + + private KeyPair localDHKeyPair; + private int localDHPrivateKeyID; + private byte[] localDHPublicKeyBytes; + private byte[] localDHPublicKeyHash; + private byte[] localDHPublicKeyEncrypted; + + private BigInteger s; + private byte[] c; + private byte[] m1; + private byte[] m2; + private byte[] cp; + private byte[] m1p; + private byte[] m2p; + + private KeyPair localLongTermKeyPair; + private Boolean isSecure = false; + private int protocolVersion; + + private int getProtocolVersion() { + return this.protocolVersion; + } + + private void setProtocolVersion(int protoVersion) { + this.protocolVersion = protoVersion; + } + + private static Logger logger = Logger.getLogger(AuthContextImpl.class + .getName()); + + class MessageFactory { + + private QueryMessage getQueryMessage() { + Vector versions = new Vector(); + versions.add(2); + return new QueryMessage(versions); + } + + private DHCommitMessage getDHCommitMessage() throws OtrException { + return new DHCommitMessage(getProtocolVersion(), + getLocalDHPublicKeyHash(), getLocalDHPublicKeyEncrypted()); + } + + private DHKeyMessage getDHKeyMessage() throws OtrException { + return new DHKeyMessage(getProtocolVersion(), + (DHPublicKey) getLocalDHKeyPair().getPublic()); + } + + private RevealSignatureMessage getRevealSignatureMessage() + throws OtrException { + try { + SignatureM m = new SignatureM((DHPublicKey) getLocalDHKeyPair() + .getPublic(), getRemoteDHPublicKey(), + getLocalLongTermKeyPair().getPublic(), + getLocalDHKeyPairID()); + + OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl(); + byte[] mhash = otrCryptoEngine.sha256Hmac(SerializationUtils + .toByteArray(m), getM1()); + byte[] signature = otrCryptoEngine.sign(mhash, + getLocalLongTermKeyPair().getPrivate()); + + SignatureX mysteriousX = new SignatureX( + getLocalLongTermKeyPair().getPublic(), + getLocalDHKeyPairID(), signature); + byte[] xEncrypted = otrCryptoEngine.aesEncrypt(getC(), null, + SerializationUtils.toByteArray(mysteriousX)); + + byte[] tmp = SerializationUtils.writeData(xEncrypted); + + byte[] xEncryptedHash = otrCryptoEngine.sha256Hmac160(tmp, + getM2()); + return new RevealSignatureMessage(getProtocolVersion(), + xEncrypted, xEncryptedHash, getR()); + } catch (IOException e) { + throw new OtrException(e); + } + } + + private SignatureMessage getSignatureMessage() throws OtrException { + SignatureM m = new SignatureM((DHPublicKey) getLocalDHKeyPair() + .getPublic(), getRemoteDHPublicKey(), + getLocalLongTermKeyPair().getPublic(), + getLocalDHKeyPairID()); + + OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl(); + byte[] mhash; + try { + mhash = otrCryptoEngine.sha256Hmac(SerializationUtils + .toByteArray(m), getM1p()); + } catch (IOException e) { + throw new OtrException(e); + } + + byte[] signature = otrCryptoEngine.sign(mhash, + getLocalLongTermKeyPair().getPrivate()); + + SignatureX mysteriousX = new SignatureX(getLocalLongTermKeyPair() + .getPublic(), getLocalDHKeyPairID(), signature); + + byte[] xEncrypted; + try { + xEncrypted = otrCryptoEngine.aesEncrypt(getCp(), null, + SerializationUtils.toByteArray(mysteriousX)); + byte[] tmp = SerializationUtils.writeData(xEncrypted); + byte[] xEncryptedHash = otrCryptoEngine.sha256Hmac160(tmp, + getM2p()); + return new SignatureMessage(getProtocolVersion(), xEncrypted, + xEncryptedHash); + } catch (IOException e) { + throw new OtrException(e); + } + } + } + + private MessageFactory messageFactory = new MessageFactory(); + + public void reset() { + logger.finest("Resetting authentication state."); + authenticationState = AuthContext.NONE; + r = null; + + remoteDHPublicKey = null; + remoteDHPublicKeyEncrypted = null; + remoteDHPublicKeyHash = null; + + localDHKeyPair = null; + localDHPrivateKeyID = 1; + localDHPublicKeyBytes = null; + localDHPublicKeyHash = null; + localDHPublicKeyEncrypted = null; + + s = null; + c = m1 = m2 = cp = m1p = m2p = null; + + localLongTermKeyPair = null; + protocolVersion = 0; + setIsSecure(false); + } + + private void setIsSecure(Boolean isSecure) { + this.isSecure = isSecure; + } + + public boolean getIsSecure() { + return isSecure; + } + + private void setAuthenticationState(int authenticationState) { + this.authenticationState = authenticationState; + } + + private int getAuthenticationState() { + return authenticationState; + } + + private byte[] getR() { + if (r == null) { + logger.finest("Picking random key r."); + r = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH]; + new Random().nextBytes(r); + } + return r; + } + + private void setRemoteDHPublicKey(DHPublicKey dhPublicKey) { + // Verifies that Alice's gy is a legal value (2 <= gy <= modulus-2) + if (dhPublicKey.getY().compareTo(OtrCryptoEngine.MODULUS_MINUS_TWO) > 0) { + throw new IllegalArgumentException( + "Illegal D-H Public Key value, Ignoring message."); + } else if (dhPublicKey.getY().compareTo(OtrCryptoEngine.BIGINTEGER_TWO) < 0) { + throw new IllegalArgumentException( + "Illegal D-H Public Key value, Ignoring message."); + } + logger.finest("Received D-H Public Key is a legal value."); + + this.remoteDHPublicKey = dhPublicKey; + } + + public DHPublicKey getRemoteDHPublicKey() { + return remoteDHPublicKey; + } + + private void setRemoteDHPublicKeyEncrypted(byte[] remoteDHPublicKeyEncrypted) { + logger.finest("Storing encrypted remote public key."); + this.remoteDHPublicKeyEncrypted = remoteDHPublicKeyEncrypted; + } + + private byte[] getRemoteDHPublicKeyEncrypted() { + return remoteDHPublicKeyEncrypted; + } + + private void setRemoteDHPublicKeyHash(byte[] remoteDHPublicKeyHash) { + logger.finest("Storing encrypted remote public key hash."); + this.remoteDHPublicKeyHash = remoteDHPublicKeyHash; + } + + private byte[] getRemoteDHPublicKeyHash() { + return remoteDHPublicKeyHash; + } + + public KeyPair getLocalDHKeyPair() throws OtrException { + if (localDHKeyPair == null) { + localDHKeyPair = new OtrCryptoEngineImpl().generateDHKeyPair(); + logger.finest("Generated local D-H key pair."); + } + return localDHKeyPair; + } + + private int getLocalDHKeyPairID() { + return localDHPrivateKeyID; + } + + private byte[] getLocalDHPublicKeyHash() throws OtrException { + if (localDHPublicKeyHash == null) { + localDHPublicKeyHash = new OtrCryptoEngineImpl() + .sha256Hash(getLocalDHPublicKeyBytes()); + logger.finest("Hashed local D-H public key."); + } + return localDHPublicKeyHash; + } + + private byte[] getLocalDHPublicKeyEncrypted() throws OtrException { + if (localDHPublicKeyEncrypted == null) { + localDHPublicKeyEncrypted = new OtrCryptoEngineImpl().aesEncrypt( + getR(), null, getLocalDHPublicKeyBytes()); + logger.finest("Encrypted our D-H public key."); + } + return localDHPublicKeyEncrypted; + } + + public BigInteger getS() throws OtrException { + if (s == null) { + s = new OtrCryptoEngineImpl().generateSecret(this + .getLocalDHKeyPair().getPrivate(), this + .getRemoteDHPublicKey()); + logger.finest("Generated shared secret."); + } + return s; + } + + private byte[] getC() throws OtrException { + if (c != null) + return c; + + byte[] h2 = h2(C_START); + ByteBuffer buff = ByteBuffer.wrap(h2); + this.c = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH]; + buff.get(this.c); + logger.finest("Computed c."); + return c; + + } + + private byte[] getM1() throws OtrException { + if (m1 != null) + return m1; + + byte[] h2 = h2(M1_START); + ByteBuffer buff = ByteBuffer.wrap(h2); + byte[] m1 = new byte[OtrCryptoEngine.SHA256_HMAC_KEY_BYTE_LENGTH]; + buff.get(m1); + logger.finest("Computed m1."); + this.m1 = m1; + return m1; + } + + private byte[] getM2() throws OtrException { + if (m2 != null) + return m2; + + byte[] h2 = h2(M2_START); + ByteBuffer buff = ByteBuffer.wrap(h2); + byte[] m2 = new byte[OtrCryptoEngine.SHA256_HMAC_KEY_BYTE_LENGTH]; + buff.get(m2); + logger.finest("Computed m2."); + this.m2 = m2; + return m2; + } + + private byte[] getCp() throws OtrException { + if (cp != null) + return cp; + + byte[] h2 = h2(C_START); + ByteBuffer buff = ByteBuffer.wrap(h2); + byte[] cp = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH]; + buff.position(OtrCryptoEngine.AES_KEY_BYTE_LENGTH); + buff.get(cp); + logger.finest("Computed c'."); + this.cp = cp; + return cp; + } + + private byte[] getM1p() throws OtrException { + if (m1p != null) + return m1p; + + byte[] h2 = h2(M1p_START); + ByteBuffer buff = ByteBuffer.wrap(h2); + byte[] m1p = new byte[OtrCryptoEngine.SHA256_HMAC_KEY_BYTE_LENGTH]; + buff.get(m1p); + this.m1p = m1p; + logger.finest("Computed m1'."); + return m1p; + } + + private byte[] getM2p() throws OtrException { + if (m2p != null) + return m2p; + + byte[] h2 = h2(M2p_START); + ByteBuffer buff = ByteBuffer.wrap(h2); + byte[] m2p = new byte[OtrCryptoEngine.SHA256_HMAC_KEY_BYTE_LENGTH]; + buff.get(m2p); + this.m2p = m2p; + logger.finest("Computed m2'."); + return m2p; + } + + public KeyPair getLocalLongTermKeyPair() { + if (localLongTermKeyPair == null) { + localLongTermKeyPair = getSession().getLocalKeyPair(); + } + return localLongTermKeyPair; + } + + private byte[] h2(byte b) throws OtrException { + byte[] secbytes; + try { + secbytes = SerializationUtils.writeMpi(getS()); + } catch (IOException e) { + throw new OtrException(e); + } + + int len = secbytes.length + 1; + ByteBuffer buff = ByteBuffer.allocate(len); + buff.put(b); + buff.put(secbytes); + byte[] sdata = buff.array(); + return new OtrCryptoEngineImpl().sha256Hash(sdata); + } + + private byte[] getLocalDHPublicKeyBytes() throws OtrException { + if (localDHPublicKeyBytes == null) { + try { + this.localDHPublicKeyBytes = SerializationUtils + .writeMpi(((DHPublicKey) getLocalDHKeyPair() + .getPublic()).getY()); + + } catch (IOException e) { + throw new OtrException(e); + } + + } + return localDHPublicKeyBytes; + } + + public void handleReceivingMessage(AbstractMessage m) throws OtrException { + + switch (m.messageType) { + case AbstractEncodedMessage.MESSAGE_DH_COMMIT: + handleDHCommitMessage((DHCommitMessage) m); + break; + case AbstractEncodedMessage.MESSAGE_DHKEY: + handleDHKeyMessage((DHKeyMessage) m); + break; + case AbstractEncodedMessage.MESSAGE_REVEALSIG: + handleRevealSignatureMessage((RevealSignatureMessage) m); + break; + case AbstractEncodedMessage.MESSAGE_SIGNATURE: + handleSignatureMessage((SignatureMessage) m); + break; + default: + throw new UnsupportedOperationException(); + } + } + + private void handleSignatureMessage(SignatureMessage m) throws OtrException { + Session session = getSession(); + SessionID sessionID = session.getSessionID(); + logger.finest(sessionID.getAccountID() + + " received a signature message from " + sessionID.getUserID() + + " throught " + sessionID.getProtocolName() + "."); + if (!session.getSessionPolicy().getAllowV2()) { + logger.finest("Policy does not allow OTRv2, ignoring message."); + return; + } + + switch (this.getAuthenticationState()) { + case AWAITING_SIG: + // Verify MAC. + if (!m.verify(this.getM2p())) { + logger + .finest("Signature MACs are not equal, ignoring message."); + return; + } + + // Decrypt X. + byte[] remoteXDecrypted = m.decrypt(this.getCp()); + SignatureX remoteX; + try { + remoteX = SerializationUtils.toMysteriousX(remoteXDecrypted); + } catch (IOException e) { + throw new OtrException(e); + } + // Compute signature. + PublicKey remoteLongTermPublicKey = remoteX.longTermPublicKey; + SignatureM remoteM = new SignatureM(this.getRemoteDHPublicKey(), + (DHPublicKey) this.getLocalDHKeyPair().getPublic(), + remoteLongTermPublicKey, remoteX.dhKeyID); + OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl(); + // Verify signature. + byte[] signature; + try { + signature = otrCryptoEngine.sha256Hmac(SerializationUtils + .toByteArray(remoteM), this.getM1p()); + } catch (IOException e) { + throw new OtrException(e); + } + if (!otrCryptoEngine.verify(signature, remoteLongTermPublicKey, + remoteX.signature)) { + logger.finest("Signature verification failed."); + return; + } + + this.setIsSecure(true); + this.setRemoteLongTermPublicKey(remoteLongTermPublicKey); + break; + default: + logger + .finest("We were not expecting a signature, ignoring message."); + return; + } + } + + private void handleRevealSignatureMessage(RevealSignatureMessage m) + throws OtrException { + Session session = getSession(); + SessionID sessionID = session.getSessionID(); + logger.finest(sessionID.getAccountID() + + " received a reveal signature message from " + + sessionID.getUserID() + " throught " + + sessionID.getProtocolName() + "."); + + if (!session.getSessionPolicy().getAllowV2()) { + logger.finest("Policy does not allow OTRv2, ignoring message."); + return; + } + + switch (this.getAuthenticationState()) { + case AWAITING_REVEALSIG: + // Use the received value of r to decrypt the value of gx + // received + // in the D-H Commit Message, and verify the hash therein. + // Decrypt + // the encrypted signature, and verify the signature and the + // MACs. + // If everything checks out: + + // * Reply with a Signature Message. + // * Transition authstate to AUTHSTATE_NONE. + // * Transition msgstate to MSGSTATE_ENCRYPTED. + // * TODO If there is a recent stored message, encrypt it and + // send + // it as a Data Message. + + OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl(); + // Uses r to decrypt the value of gx sent earlier + byte[] remoteDHPublicKeyDecrypted = otrCryptoEngine.aesDecrypt( + m.revealedKey, null, this.getRemoteDHPublicKeyEncrypted()); + + // Verifies that HASH(gx) matches the value sent earlier + byte[] remoteDHPublicKeyHash = otrCryptoEngine + .sha256Hash(remoteDHPublicKeyDecrypted); + if (!Arrays.equals(remoteDHPublicKeyHash, this + .getRemoteDHPublicKeyHash())) { + logger.finest("Hashes don't match, ignoring message."); + return; + } + + // Verifies that Bob's gx is a legal value (2 <= gx <= + // modulus-2) + BigInteger remoteDHPublicKeyMpi; + try { + remoteDHPublicKeyMpi = SerializationUtils + .readMpi(remoteDHPublicKeyDecrypted); + } catch (IOException e) { + throw new OtrException(e); + } + + this.setRemoteDHPublicKey(otrCryptoEngine + .getDHPublicKey(remoteDHPublicKeyMpi)); + + // Verify received Data. + if (!m.verify(this.getM2())) { + logger + .finest("Signature MACs are not equal, ignoring message."); + return; + } + + // Decrypt X. + byte[] remoteXDecrypted = m.decrypt(this.getC()); + SignatureX remoteX; + try { + remoteX = SerializationUtils.toMysteriousX(remoteXDecrypted); + } catch (IOException e) { + throw new OtrException(e); + } + + // Compute signature. + PublicKey remoteLongTermPublicKey = remoteX.longTermPublicKey; + SignatureM remoteM = new SignatureM(this.getRemoteDHPublicKey(), + (DHPublicKey) this.getLocalDHKeyPair().getPublic(), + remoteLongTermPublicKey, remoteX.dhKeyID); + + // Verify signature. + byte[] signature; + try { + signature = otrCryptoEngine.sha256Hmac(SerializationUtils + .toByteArray(remoteM), this.getM1()); + } catch (IOException e) { + throw new OtrException(e); + } + + if (!otrCryptoEngine.verify(signature, remoteLongTermPublicKey, + remoteX.signature)) { + logger.finest("Signature verification failed."); + return; + } + + logger.finest("Signature verification succeeded."); + + this.setAuthenticationState(AuthContext.NONE); + this.setIsSecure(true); + this.setRemoteLongTermPublicKey(remoteLongTermPublicKey); + getSession().injectMessage(messageFactory.getSignatureMessage()); + break; + default: + logger.finest("Ignoring message."); + break; + } + } + + private void handleDHKeyMessage(DHKeyMessage m) throws OtrException { + Session session = getSession(); + SessionID sessionID = session.getSessionID(); + logger.finest(sessionID.getAccountID() + + " received a D-H key message from " + sessionID.getUserID() + + " throught " + sessionID.getProtocolName() + "."); + + if (!session.getSessionPolicy().getAllowV2()) { + logger.finest("If ALLOW_V2 is not set, ignore this message."); + return; + } + + switch (this.getAuthenticationState()) { + case AWAITING_DHKEY: + // Reply with a Reveal Signature Message and transition + // authstate to + // AUTHSTATE_AWAITING_SIG + this.setRemoteDHPublicKey(m.dhPublicKey); + this.setAuthenticationState(AuthContext.AWAITING_SIG); + getSession().injectMessage( + messageFactory.getRevealSignatureMessage()); + logger.finest("Sent Reveal Signature."); + break; + case AWAITING_SIG: + + if (m.dhPublicKey.getY().equals(this.getRemoteDHPublicKey().getY())) { + // If this D-H Key message is the same the one you received + // earlier (when you entered AUTHSTATE_AWAITING_SIG): + // Retransmit + // your Reveal Signature Message. + getSession().injectMessage( + messageFactory.getRevealSignatureMessage()); + logger.finest("Resent Reveal Signature."); + } else { + // Otherwise: Ignore the message. + logger.finest("Ignoring message."); + } + break; + default: + // Ignore the message + break; + } + } + + private void handleDHCommitMessage(DHCommitMessage m) throws OtrException { + Session session = getSession(); + SessionID sessionID = session.getSessionID(); + logger.finest(sessionID.getAccountID() + + " received a D-H commit message from " + + sessionID.getUserID() + " throught " + + sessionID.getProtocolName() + "."); + + if (!session.getSessionPolicy().getAllowV2()) { + logger.finest("ALLOW_V2 is not set, ignore this message."); + return; + } + + switch (this.getAuthenticationState()) { + case NONE: + // Reply with a D-H Key Message, and transition authstate to + // AUTHSTATE_AWAITING_REVEALSIG. + this.reset(); + this.setProtocolVersion(2); + this.setRemoteDHPublicKeyEncrypted(m.dhPublicKeyEncrypted); + this.setRemoteDHPublicKeyHash(m.dhPublicKeyHash); + this.setAuthenticationState(AuthContext.AWAITING_REVEALSIG); + getSession().injectMessage(messageFactory.getDHKeyMessage()); + logger.finest("Sent D-H key."); + break; + + case AWAITING_DHKEY: + // This is the trickiest transition in the whole protocol. It + // indicates that you have already sent a D-H Commit message to + // your + // correspondent, but that he either didn't receive it, or just + // didn't receive it yet, and has sent you one as well. The + // symmetry + // will be broken by comparing the hashed gx you sent in your + // D-H + // Commit Message with the one you received, considered as + // 32-byte + // unsigned big-endian values. + BigInteger ourHash = new BigInteger(1, this + .getLocalDHPublicKeyHash()); + BigInteger theirHash = new BigInteger(1, m.dhPublicKeyHash); + + if (theirHash.compareTo(ourHash) == -1) { + // Ignore the incoming D-H Commit message, but resend your + // D-H + // Commit message. + getSession().injectMessage(messageFactory.getDHCommitMessage()); + logger + .finest("Ignored the incoming D-H Commit message, but resent our D-H Commit message."); + } else { + // *Forget* your old gx value that you sent (encrypted) + // earlier, + // and pretend you're in AUTHSTATE_NONE; i.e. reply with a + // D-H + // Key Message, and transition authstate to + // AUTHSTATE_AWAITING_REVEALSIG. + this.reset(); + this.setProtocolVersion(2); + this.setRemoteDHPublicKeyEncrypted(m.dhPublicKeyEncrypted); + this.setRemoteDHPublicKeyHash(m.dhPublicKeyHash); + this.setAuthenticationState(AuthContext.AWAITING_REVEALSIG); + getSession().injectMessage(messageFactory.getDHKeyMessage()); + logger + .finest("Forgot our old gx value that we sent (encrypted) earlier, and pretended we're in AUTHSTATE_NONE -> Sent D-H key."); + } + break; + + case AWAITING_REVEALSIG: + // Retransmit your D-H Key Message (the same one as you sent + // when + // you entered AUTHSTATE_AWAITING_REVEALSIG). Forget the old D-H + // Commit message, and use this new one instead. + this.setRemoteDHPublicKeyEncrypted(m.dhPublicKeyEncrypted); + this.setRemoteDHPublicKeyHash(m.dhPublicKeyHash); + getSession().injectMessage(messageFactory.getDHKeyMessage()); + logger.finest("Sent D-H key."); + break; + case AWAITING_SIG: + // Reply with a new D-H Key message, and transition authstate to + // AUTHSTATE_AWAITING_REVEALSIG + this.reset(); + this.setRemoteDHPublicKeyEncrypted(m.dhPublicKeyEncrypted); + this.setRemoteDHPublicKeyHash(m.dhPublicKeyHash); + this.setAuthenticationState(AuthContext.AWAITING_REVEALSIG); + getSession().injectMessage(messageFactory.getDHKeyMessage()); + logger.finest("Sent D-H key."); + break; + case V1_SETUP: + throw new UnsupportedOperationException(); + } + } + + public void startV2Auth() throws OtrException { + logger + .finest("Starting Authenticated Key Exchange, sending query message"); + getSession().injectMessage(messageFactory.getQueryMessage()); + } + + public void respondV2Auth() throws OtrException { + logger.finest("Responding to Query Message"); + this.reset(); + this.setProtocolVersion(2); + this.setAuthenticationState(AuthContext.AWAITING_DHKEY); + logger.finest("Sending D-H Commit."); + getSession().injectMessage(messageFactory.getDHCommitMessage()); + } + + private void setSession(Session session) { + this.session = session; + } + + private Session getSession() { + return session; + } + + private PublicKey remoteLongTermPublicKey; + + public PublicKey getRemoteLongTermPublicKey() { + return remoteLongTermPublicKey; + } + + private void setRemoteLongTermPublicKey(PublicKey pubKey) { + this.remoteLongTermPublicKey = pubKey; + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/session/Session.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/session/Session.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,42 @@ +package net.java.otr4j.session; + +import java.security.KeyPair; +import java.security.PublicKey; +import java.util.List; + +import net.java.otr4j.OtrEngineListener; +import net.java.otr4j.OtrException; +import net.java.otr4j.OtrPolicy; +import net.java.otr4j.io.messages.AbstractMessage; +import net.java.otr4j.session.SessionImpl.TLV; + +public interface Session { + + public abstract SessionStatus getSessionStatus(); + + public abstract SessionID getSessionID(); + + public abstract void injectMessage(AbstractMessage m) throws OtrException; + + public abstract KeyPair getLocalKeyPair(); + + public abstract OtrPolicy getSessionPolicy(); + + public abstract String transformReceiving(String content) + throws OtrException; + + public abstract String transformSending(String content, List tlvs) + throws OtrException; + + public abstract void startSession() throws OtrException; + + public abstract void endSession() throws OtrException; + + public abstract void refreshSession() throws OtrException; + + public abstract PublicKey getRemotePublicKey(); + + public abstract void addOtrEngineListener(OtrEngineListener l); + + public abstract void removeOtrEngineListener(OtrEngineListener l); +} \ No newline at end of file diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/session/SessionID.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/session/SessionID.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,70 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.session; + +/** + * + * @author George Politis + * + */ +public final class SessionID { + + public SessionID(String accountID, String userID, String protocolName) { + this.setAccountID(accountID); + this.setUserID(userID); + this.setProtocolName(protocolName); + } + + private String accountID; + private String userID; + private String protocolName; + public static final SessionID Empty = new SessionID(null, null, null); + + public void setAccountID(String accountID) { + this.accountID = accountID; + } + + public String getAccountID() { + return accountID; + } + + private void setUserID(String userID) { + this.userID = userID; + } + + public String getUserID() { + return userID; + } + + private void setProtocolName(String protocolName) { + this.protocolName = protocolName; + } + + public String getProtocolName() { + return protocolName; + } + + public String toString() { + return this.getAccountID() + "_" + this.getProtocolName() + "_" + + this.getUserID(); + } + + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj == null || obj.getClass() != this.getClass()) + return false; + + SessionID sessionID = (SessionID) obj; + + return this.toString().equals(sessionID.toString()); + } + + public int hashCode() { + return this.toString().hashCode(); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/session/SessionImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/session/SessionImpl.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,792 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +package net.java.otr4j.session; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.security.KeyPair; +import java.security.PublicKey; +import java.util.Arrays; +import java.util.List; +import java.util.Vector; +import java.util.logging.Logger; +import javax.crypto.interfaces.DHPublicKey; + +import net.java.otr4j.OtrEngineHost; +import net.java.otr4j.OtrEngineListener; +import net.java.otr4j.OtrException; +import net.java.otr4j.OtrPolicy; +import net.java.otr4j.crypto.OtrCryptoEngine; +import net.java.otr4j.crypto.OtrCryptoEngineImpl; +import net.java.otr4j.io.OtrInputStream; +import net.java.otr4j.io.OtrOutputStream; +import net.java.otr4j.io.SerializationConstants; +import net.java.otr4j.io.SerializationUtils; +import net.java.otr4j.io.messages.DataMessage; +import net.java.otr4j.io.messages.AbstractEncodedMessage; +import net.java.otr4j.io.messages.ErrorMessage; +import net.java.otr4j.io.messages.AbstractMessage; +import net.java.otr4j.io.messages.MysteriousT; +import net.java.otr4j.io.messages.PlainTextMessage; +import net.java.otr4j.io.messages.QueryMessage; + +/** + * + * @author George Politis + */ +public class SessionImpl implements Session { + + class TLV { + public TLV(int type, byte[] value) { + this.setType(type); + this.setValue(value); + } + + public void setType(int type) { + this.type = type; + } + + public int getType() { + return type; + } + + public void setValue(byte[] value) { + this.value = value; + } + + public byte[] getValue() { + return value; + } + + private int type; + private byte[] value; + } + + private SessionID sessionID; + private OtrEngineHost host; + private SessionStatus sessionStatus; + private AuthContext authContext; + private SessionKeys[][] sessionKeys; + private Vector oldMacKeys; + private static Logger logger = Logger + .getLogger(SessionImpl.class.getName()); + + public SessionImpl(SessionID sessionID, OtrEngineHost listener) { + + this.setSessionID(sessionID); + this.setHost(listener); + + // client application calls OtrEngine.getSessionStatus() + // -> create new session if it does not exist, end up here + // -> setSessionStatus() fires statusChangedEvent + // -> client application calls OtrEngine.getSessionStatus() + this.sessionStatus = SessionStatus.PLAINTEXT; + } + + private SessionKeys getEncryptionSessionKeys() { + logger.finest("Getting encryption keys"); + return getSessionKeysByIndex(SessionKeys.Previous, SessionKeys.Current); + } + + private SessionKeys getMostRecentSessionKeys() { + logger.finest("Getting most recent keys."); + return getSessionKeysByIndex(SessionKeys.Current, SessionKeys.Current); + } + + private SessionKeys getSessionKeysByID(int localKeyID, int remoteKeyID) { + logger + .finest("Searching for session keys with (localKeyID, remoteKeyID) = (" + + localKeyID + "," + remoteKeyID + ")"); + + for (int i = 0; i < getSessionKeys().length; i++) { + for (int j = 0; j < getSessionKeys()[i].length; j++) { + SessionKeys current = getSessionKeysByIndex(i, j); + if (current.getLocalKeyID() == localKeyID + && current.getRemoteKeyID() == remoteKeyID) { + logger.finest("Matching keys found."); + return current; + } + } + } + + return null; + } + + private SessionKeys getSessionKeysByIndex(int localKeyIndex, + int remoteKeyIndex) { + if (getSessionKeys()[localKeyIndex][remoteKeyIndex] == null) + getSessionKeys()[localKeyIndex][remoteKeyIndex] = new SessionKeysImpl( + localKeyIndex, remoteKeyIndex); + + return getSessionKeys()[localKeyIndex][remoteKeyIndex]; + } + + private void rotateRemoteSessionKeys(DHPublicKey pubKey) + throws OtrException { + + logger.finest("Rotating remote keys."); + SessionKeys sess1 = getSessionKeysByIndex(SessionKeys.Current, + SessionKeys.Previous); + if (sess1.getIsUsedReceivingMACKey()) { + logger + .finest("Detected used Receiving MAC key. Adding to old MAC keys to reveal it."); + getOldMacKeys().add(sess1.getReceivingMACKey()); + } + + SessionKeys sess2 = getSessionKeysByIndex(SessionKeys.Previous, + SessionKeys.Previous); + if (sess2.getIsUsedReceivingMACKey()) { + logger + .finest("Detected used Receiving MAC key. Adding to old MAC keys to reveal it."); + getOldMacKeys().add(sess2.getReceivingMACKey()); + } + + SessionKeys sess3 = getSessionKeysByIndex(SessionKeys.Current, + SessionKeys.Current); + sess1 + .setRemoteDHPublicKey(sess3.getRemoteKey(), sess3 + .getRemoteKeyID()); + + SessionKeys sess4 = getSessionKeysByIndex(SessionKeys.Previous, + SessionKeys.Current); + sess2 + .setRemoteDHPublicKey(sess4.getRemoteKey(), sess4 + .getRemoteKeyID()); + + sess3.setRemoteDHPublicKey(pubKey, sess3.getRemoteKeyID() + 1); + sess4.setRemoteDHPublicKey(pubKey, sess4.getRemoteKeyID() + 1); + } + + private void rotateLocalSessionKeys() throws OtrException { + + logger.finest("Rotating local keys."); + SessionKeys sess1 = getSessionKeysByIndex(SessionKeys.Previous, + SessionKeys.Current); + if (sess1.getIsUsedReceivingMACKey()) { + logger + .finest("Detected used Receiving MAC key. Adding to old MAC keys to reveal it."); + getOldMacKeys().add(sess1.getReceivingMACKey()); + } + + SessionKeys sess2 = getSessionKeysByIndex(SessionKeys.Previous, + SessionKeys.Previous); + if (sess2.getIsUsedReceivingMACKey()) { + logger + .finest("Detected used Receiving MAC key. Adding to old MAC keys to reveal it."); + getOldMacKeys().add(sess2.getReceivingMACKey()); + } + + SessionKeys sess3 = getSessionKeysByIndex(SessionKeys.Current, + SessionKeys.Current); + sess1.setLocalPair(sess3.getLocalPair(), sess3.getLocalKeyID()); + SessionKeys sess4 = getSessionKeysByIndex(SessionKeys.Current, + SessionKeys.Previous); + sess2.setLocalPair(sess4.getLocalPair(), sess4.getLocalKeyID()); + + KeyPair newPair = new OtrCryptoEngineImpl().generateDHKeyPair(); + sess3.setLocalPair(newPair, sess3.getLocalKeyID() + 1); + sess4.setLocalPair(newPair, sess4.getLocalKeyID() + 1); + } + + private byte[] collectOldMacKeys() { + logger.finest("Collecting old MAC keys to be revealed."); + int len = 0; + for (int i = 0; i < getOldMacKeys().size(); i++) + len += getOldMacKeys().get(i).length; + + ByteBuffer buff = ByteBuffer.allocate(len); + for (int i = 0; i < getOldMacKeys().size(); i++) + buff.put(getOldMacKeys().get(i)); + + getOldMacKeys().clear(); + return buff.array(); + } + + private void setSessionStatus(SessionStatus sessionStatus) + throws OtrException { + + if (sessionStatus == this.sessionStatus) + return; + + switch (sessionStatus) { + case ENCRYPTED: + AuthContext auth = this.getAuthContext(); + logger.finest("Setting most recent session keys from auth."); + for (int i = 0; i < this.getSessionKeys()[0].length; i++) { + SessionKeys current = getSessionKeysByIndex(0, i); + current.setLocalPair(auth.getLocalDHKeyPair(), 1); + current.setRemoteDHPublicKey(auth.getRemoteDHPublicKey(), 1); + current.setS(auth.getS()); + } + + KeyPair nextDH = new OtrCryptoEngineImpl().generateDHKeyPair(); + for (int i = 0; i < this.getSessionKeys()[1].length; i++) { + SessionKeys current = getSessionKeysByIndex(1, i); + current.setRemoteDHPublicKey(auth.getRemoteDHPublicKey(), 1); + current.setLocalPair(nextDH, 2); + } + + this.setRemotePublicKey(auth.getRemoteLongTermPublicKey()); + + auth.reset(); + break; + } + + this.sessionStatus = sessionStatus; + + for (OtrEngineListener l : this.listeners) + l.sessionStatusChanged(getSessionID()); + } + + /* + * (non-Javadoc) + * + * @see net.java.otr4j.session.ISession#getSessionStatus() + */ + + public SessionStatus getSessionStatus() { + return sessionStatus; + } + + private void setSessionID(SessionID sessionID) { + this.sessionID = sessionID; + } + + /* + * (non-Javadoc) + * + * @see net.java.otr4j.session.ISession#getSessionID() + */ + public SessionID getSessionID() { + return sessionID; + } + + private void setHost(OtrEngineHost host) { + this.host = host; + } + + private OtrEngineHost getHost() { + return host; + } + + private SessionKeys[][] getSessionKeys() { + if (sessionKeys == null) + sessionKeys = new SessionKeys[2][2]; + return sessionKeys; + } + + private AuthContext getAuthContext() { + if (authContext == null) + authContext = new AuthContextImpl(this); + return authContext; + } + + private Vector getOldMacKeys() { + if (oldMacKeys == null) + oldMacKeys = new Vector(); + return oldMacKeys; + } + + /* + * (non-Javadoc) + * + * @see + * net.java.otr4j.session.ISession#handleReceivingMessage(java.lang.String) + */ + public String transformReceiving(String msgText) throws OtrException { + OtrPolicy policy = getSessionPolicy(); + if (!policy.getAllowV1() && !policy.getAllowV2()) { + logger + .finest("Policy does not allow neither V1 not V2, ignoring message."); + return msgText; + } + + AbstractMessage m; + try { + m = SerializationUtils.toMessage(msgText); + } catch (IOException e) { + throw new OtrException(e); + } + + if (m == null) + return msgText; // Propably null or empty. + + switch (m.messageType) { + case AbstractEncodedMessage.MESSAGE_DATA: + return handleDataMessage((DataMessage) m); + case AbstractMessage.MESSAGE_ERROR: + handleErrorMessage((ErrorMessage) m); + return null; + case AbstractMessage.MESSAGE_PLAINTEXT: + return handlePlainTextMessage((PlainTextMessage) m); + case AbstractMessage.MESSAGE_QUERY: + handleQueryMessage((QueryMessage) m); + return null; + case AbstractEncodedMessage.MESSAGE_DH_COMMIT: + case AbstractEncodedMessage.MESSAGE_DHKEY: + case AbstractEncodedMessage.MESSAGE_REVEALSIG: + case AbstractEncodedMessage.MESSAGE_SIGNATURE: + AuthContext auth = this.getAuthContext(); + auth.handleReceivingMessage(m); + + if (auth.getIsSecure()) { + this.setSessionStatus(SessionStatus.ENCRYPTED); + logger.finest("Gone Secure."); + } + return null; + default: + throw new UnsupportedOperationException( + "Received an uknown message type."); + } + } + + private void handleQueryMessage(QueryMessage queryMessage) + throws OtrException { + logger.finest(getSessionID().getAccountID() + + " received a query message from " + + getSessionID().getUserID() + " throught " + + getSessionID().getProtocolName() + "."); + + setSessionStatus(SessionStatus.PLAINTEXT); + + OtrPolicy policy = getSessionPolicy(); + if (queryMessage.versions.contains(2) && policy.getAllowV2()) { + logger.finest("Query message with V2 support found."); + getAuthContext().respondV2Auth(); + } else if (queryMessage.versions.contains(1) && policy.getAllowV1()) { + throw new UnsupportedOperationException(); + } + } + + private void handleErrorMessage(ErrorMessage errorMessage) + throws OtrException { + logger.finest(getSessionID().getAccountID() + + " received an error message from " + + getSessionID().getUserID() + " throught " + + getSessionID().getUserID() + "."); + + getHost().showError(this.getSessionID(), errorMessage.error); + + OtrPolicy policy = getSessionPolicy(); + if (policy.getErrorStartAKE()) { + logger.finest("Error message starts AKE."); + Vector versions = new Vector(); + if (policy.getAllowV1()) + versions.add(1); + + if (policy.getAllowV2()) + versions.add(2); + + logger.finest("Sending Query"); + injectMessage(new QueryMessage(versions)); + } + } + + private String handleDataMessage(DataMessage data) throws OtrException { + logger.finest(getSessionID().getAccountID() + + " received a data message from " + getSessionID().getUserID() + + "."); + + switch (this.getSessionStatus()) { + case ENCRYPTED: + logger + .finest("Message state is ENCRYPTED. Trying to decrypt message."); + + // Find matching session keys. + int senderKeyID = data.senderKeyID; + int receipientKeyID = data.recipientKeyID; + SessionKeys matchingKeys = this.getSessionKeysByID(receipientKeyID, + senderKeyID); + + if (matchingKeys == null) { + logger.finest("No matching keys found."); + return null; + } + + // Verify received MAC with a locally calculated MAC. + logger + .finest("Transforming T to byte[] to calculate it's HmacSHA1."); + + byte[] serializedT; + try { + serializedT = SerializationUtils.toByteArray(data.getT()); + } catch (IOException e) { + throw new OtrException(e); + } + + OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl(); + + byte[] computedMAC = otrCryptoEngine.sha1Hmac(serializedT, + matchingKeys.getReceivingMACKey(), + SerializationConstants.TYPE_LEN_MAC); + + if (!Arrays.equals(computedMAC, data.mac)) { + logger.finest("MAC verification failed, ignoring message"); + return null; + } + + logger.finest("Computed HmacSHA1 value matches sent one."); + + // Mark this MAC key as old to be revealed. + matchingKeys.setIsUsedReceivingMACKey(true); + + matchingKeys.setReceivingCtr(data.ctr); + + byte[] dmc = otrCryptoEngine.aesDecrypt(matchingKeys + .getReceivingAESKey(), matchingKeys.getReceivingCtr(), + data.encryptedMessage); + String decryptedMsgContent; + try { + // Expect bytes to be text encoded in UTF-8. + decryptedMsgContent = new String(dmc, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new OtrException(e); + } + + logger.finest("Decrypted message: \"" + decryptedMsgContent + "\""); + + // Rotate keys if necessary. + SessionKeys mostRecent = this.getMostRecentSessionKeys(); + if (mostRecent.getLocalKeyID() == receipientKeyID) + this.rotateLocalSessionKeys(); + + if (mostRecent.getRemoteKeyID() == senderKeyID) + this.rotateRemoteSessionKeys(data.nextDH); + + // Handle TLVs + List tlvs = null; + int tlvIndex = decryptedMsgContent.indexOf((char) 0x0); + if (tlvIndex > -1) { + decryptedMsgContent = decryptedMsgContent + .substring(0, tlvIndex); + tlvIndex++; + byte[] tlvsb = new byte[dmc.length - tlvIndex]; + System.arraycopy(dmc, tlvIndex, tlvsb, 0, tlvsb.length); + + tlvs = new Vector(); + ByteArrayInputStream tin = new ByteArrayInputStream(tlvsb); + while (tin.available() > 0) { + int type; + byte[] tdata; + OtrInputStream eois = new OtrInputStream(tin); + try { + type = eois.readShort(); + tdata = eois.readTlvData(); + eois.close(); + } catch (IOException e) { + throw new OtrException(e); + } + + tlvs.add(new TLV(type, tdata)); + } + } + if (tlvs != null && tlvs.size() > 0) { + for (TLV tlv : tlvs) { + switch (tlv.getType()) { + case 1: + this.setSessionStatus(SessionStatus.FINISHED); + return null; + default: + return decryptedMsgContent; + } + } + } + + return decryptedMsgContent; + + case FINISHED: + case PLAINTEXT: + getHost().showWarning(this.getSessionID(), + "Unreadable encrypted message was received."); + + injectMessage(new ErrorMessage(AbstractMessage.MESSAGE_ERROR, + "You sent me an unreadable encrypted message..")); + break; + } + + return null; + } + + public void injectMessage(AbstractMessage m) throws OtrException { + String msg; + try { + msg = SerializationUtils.toString(m); + } catch (IOException e) { + throw new OtrException(e); + } + getHost().injectMessage(getSessionID(), msg); + } + + private String handlePlainTextMessage(PlainTextMessage plainTextMessage) + throws OtrException { + logger.finest(getSessionID().getAccountID() + + " received a plaintext message from " + + getSessionID().getUserID() + " throught " + + getSessionID().getProtocolName() + "."); + + OtrPolicy policy = getSessionPolicy(); + List versions = plainTextMessage.versions; + if (versions == null || versions.size() < 1) { + logger + .finest("Received plaintext message without the whitespace tag."); + switch (this.getSessionStatus()) { + case ENCRYPTED: + case FINISHED: + // Display the message to the user, but warn him that the + // message was received unencrypted. + getHost().showWarning(this.getSessionID(), + "The message was received unencrypted."); + return plainTextMessage.cleanText; + case PLAINTEXT: + // Simply display the message to the user. If + // REQUIRE_ENCRYPTION + // is set, warn him that the message was received + // unencrypted. + if (policy.getRequireEncryption()) { + getHost().showWarning(this.getSessionID(), + "The message was received unencrypted."); + } + return plainTextMessage.cleanText; + } + } else { + logger + .finest("Received plaintext message with the whitespace tag."); + switch (this.getSessionStatus()) { + case ENCRYPTED: + case FINISHED: + // Remove the whitespace tag and display the message to the + // user, but warn him that the message was received + // unencrypted. + getHost().showWarning(this.getSessionID(), + "The message was received unencrypted."); + case PLAINTEXT: + // Remove the whitespace tag and display the message to the + // user. If REQUIRE_ENCRYPTION is set, warn him that the + // message + // was received unencrypted. + if (policy.getRequireEncryption()) + getHost().showWarning(this.getSessionID(), + "The message was received unencrypted."); + } + + if (policy.getWhitespaceStartAKE()) { + logger.finest("WHITESPACE_START_AKE is set"); + + if (plainTextMessage.versions.contains(2) + && policy.getAllowV2()) { + logger.finest("V2 tag found."); + getAuthContext().respondV2Auth(); + } else if (plainTextMessage.versions.contains(1) + && policy.getAllowV1()) { + throw new UnsupportedOperationException(); + } + } + } + + return plainTextMessage.cleanText; + } + + // Retransmit last sent message. Spec document does not mention where or + // when that should happen, must check libotr code. + private String lastSentMessage; + + public String transformSending(String msgText, List tlvs) + throws OtrException { + + switch (this.getSessionStatus()) { + case PLAINTEXT: + if (getSessionPolicy().getRequireEncryption()) { + this.lastSentMessage = msgText; + this.startSession(); + } else + // TODO this does not precisly behave according to + // specification. + return msgText; + case ENCRYPTED: + this.lastSentMessage = msgText; + logger.finest(getSessionID().getAccountID() + + " sends an encrypted message to " + + getSessionID().getUserID() + " throught " + + getSessionID().getProtocolName() + "."); + + // Get encryption keys. + SessionKeys encryptionKeys = this.getEncryptionSessionKeys(); + int senderKeyID = encryptionKeys.getLocalKeyID(); + int receipientKeyID = encryptionKeys.getRemoteKeyID(); + + // Increment CTR. + encryptionKeys.incrementSendingCtr(); + byte[] ctr = encryptionKeys.getSendingCtr(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + if (msgText != null && msgText.length() > 0) + try { + out.write(msgText.getBytes("UTF8")); + } catch (IOException e) { + throw new OtrException(e); + } + + // Append tlvs + if (tlvs != null && tlvs.size() > 0) { + out.write((byte) 0x00); + + OtrOutputStream eoos = new OtrOutputStream(out); + for (TLV tlv : tlvs) { + try { + eoos.writeShort(tlv.type); + eoos.writeTlvData(tlv.value); + } catch (IOException e) { + throw new OtrException(e); + } + } + } + + OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl(); + + byte[] data = out.toByteArray(); + // Encrypt message. + logger + .finest("Encrypting message with keyids (localKeyID, remoteKeyID) = (" + + senderKeyID + ", " + receipientKeyID + ")"); + byte[] encryptedMsg = otrCryptoEngine.aesEncrypt(encryptionKeys + .getSendingAESKey(), ctr, data); + + // Get most recent keys to get the next D-H public key. + SessionKeys mostRecentKeys = this.getMostRecentSessionKeys(); + DHPublicKey nextDH = (DHPublicKey) mostRecentKeys.getLocalPair() + .getPublic(); + + // Calculate T. + MysteriousT t = new MysteriousT(2, 0, senderKeyID, receipientKeyID, + nextDH, ctr, encryptedMsg); + + // Calculate T hash. + byte[] sendingMACKey = encryptionKeys.getSendingMACKey(); + + logger + .finest("Transforming T to byte[] to calculate it's HmacSHA1."); + byte[] serializedT; + try { + serializedT = SerializationUtils.toByteArray(t); + } catch (IOException e) { + throw new OtrException(e); + } + + byte[] mac = otrCryptoEngine.sha1Hmac(serializedT, sendingMACKey, + SerializationConstants.TYPE_LEN_MAC); + + // Get old MAC keys to be revealed. + byte[] oldKeys = this.collectOldMacKeys(); + DataMessage m = new DataMessage(t, mac, oldKeys); + + try { + return SerializationUtils.toString(m); + } catch (IOException e) { + throw new OtrException(e); + } + case FINISHED: + this.lastSentMessage = msgText; + getHost() + .showError( + sessionID, + "Your message to " + + sessionID.getUserID() + + " was not sent. Either end your private conversation, or restart it."); + return null; + default: + logger.finest("Uknown message state, not processing."); + return msgText; + } + } + + /* + * (non-Javadoc) + * + * @see net.java.otr4j.session.ISession#startSession() + */ + public void startSession() throws OtrException { + if (this.getSessionStatus() == SessionStatus.ENCRYPTED) + return; + + if (!getSessionPolicy().getAllowV2()) + throw new UnsupportedOperationException(); + + this.getAuthContext().startV2Auth(); + } + + /* + * (non-Javadoc) + * + * @see net.java.otr4j.session.ISession#endSession() + */ + public void endSession() throws OtrException { + SessionStatus status = this.getSessionStatus(); + switch (status) { + case ENCRYPTED: + Vector tlvs = new Vector(); + tlvs.add(new TLV(1, null)); + + String msg = this.transformSending(null, tlvs); + getHost().injectMessage(getSessionID(), msg); + this.setSessionStatus(SessionStatus.PLAINTEXT); + break; + case FINISHED: + this.setSessionStatus(SessionStatus.PLAINTEXT); + break; + case PLAINTEXT: + return; + } + + } + + /* + * (non-Javadoc) + * + * @see net.java.otr4j.session.ISession#refreshSession() + */ + public void refreshSession() throws OtrException { + this.endSession(); + this.startSession(); + } + + private PublicKey remotePublicKey; + + private void setRemotePublicKey(PublicKey pubKey) { + this.remotePublicKey = pubKey; + } + + public PublicKey getRemotePublicKey() { + return remotePublicKey; + } + + private List listeners = new Vector(); + + public void addOtrEngineListener(OtrEngineListener l) { + synchronized (listeners) { + if (!listeners.contains(l)) + listeners.add(l); + } + } + + public void removeOtrEngineListener(OtrEngineListener l) { + synchronized (listeners) { + listeners.remove(l); + } + } + + public OtrPolicy getSessionPolicy() { + return getHost().getSessionPolicy(getSessionID()); + } + + public KeyPair getLocalKeyPair() { + return getHost().getKeyPair(this.getSessionID()); + } +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/session/SessionKeys.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/session/SessionKeys.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,54 @@ +package net.java.otr4j.session; + +import java.math.BigInteger; +import java.security.KeyPair; + +import javax.crypto.interfaces.DHPublicKey; + +import net.java.otr4j.OtrException; + +interface SessionKeys { + + public static final int Previous = 0; + public static final int Current = 1; + public static final byte HIGH_SEND_BYTE = (byte) 0x01; + public static final byte HIGH_RECEIVE_BYTE = (byte) 0x02; + public static final byte LOW_SEND_BYTE = (byte) 0x02; + public static final byte LOW_RECEIVE_BYTE = (byte) 0x01; + + public abstract void setLocalPair(KeyPair keyPair, int localPairKeyID); + + public abstract void setRemoteDHPublicKey(DHPublicKey pubKey, + int remoteKeyID); + + public abstract void incrementSendingCtr(); + + public abstract byte[] getSendingCtr(); + + public abstract byte[] getReceivingCtr(); + + public abstract void setReceivingCtr(byte[] ctr); + + public abstract byte[] getSendingAESKey() throws OtrException; + + public abstract byte[] getReceivingAESKey() throws OtrException; + + public abstract byte[] getSendingMACKey() throws OtrException; + + public abstract byte[] getReceivingMACKey() throws OtrException; + + public abstract void setS(BigInteger s); + + public abstract void setIsUsedReceivingMACKey(Boolean isUsedReceivingMACKey); + + public abstract Boolean getIsUsedReceivingMACKey(); + + public abstract int getLocalKeyID(); + + public abstract int getRemoteKeyID(); + + public abstract DHPublicKey getRemoteKey(); + + public abstract KeyPair getLocalPair(); + +} \ No newline at end of file diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/session/SessionKeysImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/session/SessionKeysImpl.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,240 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.session; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.KeyPair; +import java.util.Arrays; +import java.util.logging.Logger; + +import javax.crypto.interfaces.DHPublicKey; + +import net.java.otr4j.OtrException; +import net.java.otr4j.crypto.OtrCryptoEngine; +import net.java.otr4j.crypto.OtrCryptoEngineImpl; +import net.java.otr4j.io.SerializationUtils; + +/** + * + * @author George Politis + */ +class SessionKeysImpl implements SessionKeys { + + private static Logger logger = Logger.getLogger(SessionKeysImpl.class + .getName()); + private String keyDescription; + + public SessionKeysImpl(int localKeyIndex, int remoteKeyIndex) { + if (localKeyIndex == 0) + keyDescription = "(Previous local, "; + else + keyDescription = "(Most recent local, "; + + if (remoteKeyIndex == 0) + keyDescription += "Previous remote)"; + else + keyDescription += "Most recent remote)"; + + } + + public void setLocalPair(KeyPair keyPair, int localPairKeyID) { + this.localPair = keyPair; + this.setLocalKeyID(localPairKeyID); + logger.finest(keyDescription + " current local key ID: " + + this.getLocalKeyID()); + this.reset(); + } + + public void setRemoteDHPublicKey(DHPublicKey pubKey, int remoteKeyID) { + this.setRemoteKey(pubKey); + this.setRemoteKeyID(remoteKeyID); + logger.finest(keyDescription + " current remote key ID: " + + this.getRemoteKeyID()); + this.reset(); + } + + private byte[] sendingCtr = new byte[16]; + private byte[] receivingCtr = new byte[16]; + + public void incrementSendingCtr() { + logger.finest("Incrementing counter for (localkeyID, remoteKeyID) = (" + + getLocalKeyID() + "," + getRemoteKeyID() + ")"); + // logger.debug("Counter prior increament: " + + // Utils.dump(sendingCtr, + // true, 16)); + for (int i = 7; i >= 0; i--) + if (++sendingCtr[i] != 0) + break; + // logger.debug("Counter after increament: " + + // Utils.dump(sendingCtr, + // true, 16)); + } + + public byte[] getSendingCtr() { + return sendingCtr; + } + + public byte[] getReceivingCtr() { + return receivingCtr; + } + + public void setReceivingCtr(byte[] ctr) { + for (int i = 0; i < ctr.length; i++) + receivingCtr[i] = ctr[i]; + } + + private void reset() { + logger.finest("Resetting " + keyDescription + " session keys."); + Arrays.fill(this.sendingCtr, (byte) 0x00); + Arrays.fill(this.receivingCtr, (byte) 0x00); + this.sendingAESKey = null; + this.receivingAESKey = null; + this.sendingMACKey = null; + this.receivingMACKey = null; + this.setIsUsedReceivingMACKey(false); + this.s = null; + if (getLocalPair() != null && getRemoteKey() != null) { + this.isHigh = ((DHPublicKey) getLocalPair().getPublic()).getY() + .abs().compareTo(getRemoteKey().getY().abs()) == 1; + } + + } + + private byte[] h1(byte b) throws OtrException { + + try { + byte[] secbytes = SerializationUtils.writeMpi(getS()); + + int len = secbytes.length + 1; + ByteBuffer buff = ByteBuffer.allocate(len); + buff.put(b); + buff.put(secbytes); + byte[] result = new OtrCryptoEngineImpl().sha1Hash(buff.array()); + return result; + } catch (Exception e) { + throw new OtrException(e); + } + } + + public byte[] getSendingAESKey() throws OtrException { + if (sendingAESKey != null) + return sendingAESKey; + + byte sendbyte = LOW_SEND_BYTE; + if (this.isHigh) + sendbyte = HIGH_SEND_BYTE; + + byte[] h1 = h1(sendbyte); + + byte[] key = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH]; + ByteBuffer buff = ByteBuffer.wrap(h1); + buff.get(key); + logger.finest("Calculated sending AES key."); + this.sendingAESKey = key; + return sendingAESKey; + } + + public byte[] getReceivingAESKey() throws OtrException { + if (receivingAESKey != null) + return receivingAESKey; + + byte receivebyte = LOW_RECEIVE_BYTE; + if (this.isHigh) + receivebyte = HIGH_RECEIVE_BYTE; + + byte[] h1 = h1(receivebyte); + + byte[] key = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH]; + ByteBuffer buff = ByteBuffer.wrap(h1); + buff.get(key); + logger.finest("Calculated receiving AES key."); + this.receivingAESKey = key; + + return receivingAESKey; + } + + public byte[] getSendingMACKey() throws OtrException { + if (sendingMACKey != null) + return sendingMACKey; + + sendingMACKey = new OtrCryptoEngineImpl().sha1Hash(getSendingAESKey()); + logger.finest("Calculated sending MAC key."); + return sendingMACKey; + } + + public byte[] getReceivingMACKey() throws OtrException { + if (receivingMACKey == null) { + receivingMACKey = new OtrCryptoEngineImpl() + .sha1Hash(getReceivingAESKey()); + logger.finest("Calculated receiving AES key."); + } + return receivingMACKey; + } + + private BigInteger getS() throws OtrException { + if (s == null) { + s = new OtrCryptoEngineImpl().generateSecret(getLocalPair() + .getPrivate(), getRemoteKey()); + logger.finest("Calculating shared secret S."); + } + return s; + } + + public void setS(BigInteger s) { + this.s = s; + } + + public void setIsUsedReceivingMACKey(Boolean isUsedReceivingMACKey) { + this.isUsedReceivingMACKey = isUsedReceivingMACKey; + } + + public Boolean getIsUsedReceivingMACKey() { + return isUsedReceivingMACKey; + } + + private void setLocalKeyID(int localKeyID) { + this.localKeyID = localKeyID; + } + + public int getLocalKeyID() { + return localKeyID; + } + + private void setRemoteKeyID(int remoteKeyID) { + this.remoteKeyID = remoteKeyID; + } + + public int getRemoteKeyID() { + return remoteKeyID; + } + + private void setRemoteKey(DHPublicKey remoteKey) { + this.remoteKey = remoteKey; + } + + public DHPublicKey getRemoteKey() { + return remoteKey; + } + + public KeyPair getLocalPair() { + return localPair; + } + + private int localKeyID; + private int remoteKeyID; + private DHPublicKey remoteKey; + private KeyPair localPair; + + private byte[] sendingAESKey; + private byte[] receivingAESKey; + private byte[] sendingMACKey; + private byte[] receivingMACKey; + private Boolean isUsedReceivingMACKey; + private BigInteger s; + private Boolean isHigh; +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/java/net/java/otr4j/session/SessionStatus.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/net/java/otr4j/session/SessionStatus.java Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,17 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.session; + +/** + * + * @author George Politis + */ +public enum SessionStatus { + PLAINTEXT, + ENCRYPTED, + FINISHED +} diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/anim/rotate_and_scale.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/anim/rotate_and_scale.xml Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,9 @@ + + + + + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/beem_icon_launcher_color.png Binary file app/src/main/res/drawable-hdpi/beem_icon_launcher_color.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_angel.png Binary file app/src/main/res/drawable-hdpi/emo_im_angel.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_cool.png Binary file app/src/main/res/drawable-hdpi/emo_im_cool.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_crying.png Binary file app/src/main/res/drawable-hdpi/emo_im_crying.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_embarrassed.png Binary file app/src/main/res/drawable-hdpi/emo_im_embarrassed.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_foot_in_mouth.png Binary file app/src/main/res/drawable-hdpi/emo_im_foot_in_mouth.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_happy.png Binary file app/src/main/res/drawable-hdpi/emo_im_happy.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_heart.png Binary file app/src/main/res/drawable-hdpi/emo_im_heart.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_kissing.png Binary file app/src/main/res/drawable-hdpi/emo_im_kissing.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_laughing.png Binary file app/src/main/res/drawable-hdpi/emo_im_laughing.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_lips_are_sealed.png Binary file app/src/main/res/drawable-hdpi/emo_im_lips_are_sealed.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_mad.png Binary file app/src/main/res/drawable-hdpi/emo_im_mad.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_money_mouth.png Binary file app/src/main/res/drawable-hdpi/emo_im_money_mouth.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_pokerface.png Binary file app/src/main/res/drawable-hdpi/emo_im_pokerface.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_sad.png Binary file app/src/main/res/drawable-hdpi/emo_im_sad.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_smirk.png Binary file app/src/main/res/drawable-hdpi/emo_im_smirk.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_surprised.png Binary file app/src/main/res/drawable-hdpi/emo_im_surprised.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_tongue_sticking_out.png Binary file app/src/main/res/drawable-hdpi/emo_im_tongue_sticking_out.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_undecided.png Binary file app/src/main/res/drawable-hdpi/emo_im_undecided.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_winking.png Binary file app/src/main/res/drawable-hdpi/emo_im_winking.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_wtf.png Binary file app/src/main/res/drawable-hdpi/emo_im_wtf.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-hdpi/emo_im_yelling.png Binary file app/src/main/res/drawable-hdpi/emo_im_yelling.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-ldpi/beem_icon_launcher_color.png Binary file app/src/main/res/drawable-ldpi/beem_icon_launcher_color.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/beem_icon_launcher_color.png Binary file app/src/main/res/drawable-mdpi/beem_icon_launcher_color.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_angel.png Binary file app/src/main/res/drawable-mdpi/emo_im_angel.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_cool.png Binary file app/src/main/res/drawable-mdpi/emo_im_cool.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_crying.png Binary file app/src/main/res/drawable-mdpi/emo_im_crying.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_embarrassed.png Binary file app/src/main/res/drawable-mdpi/emo_im_embarrassed.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_foot_in_mouth.png Binary file app/src/main/res/drawable-mdpi/emo_im_foot_in_mouth.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_happy.png Binary file app/src/main/res/drawable-mdpi/emo_im_happy.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_heart.png Binary file app/src/main/res/drawable-mdpi/emo_im_heart.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_kissing.png Binary file app/src/main/res/drawable-mdpi/emo_im_kissing.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_laughing.png Binary file app/src/main/res/drawable-mdpi/emo_im_laughing.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_lips_are_sealed.png Binary file app/src/main/res/drawable-mdpi/emo_im_lips_are_sealed.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_mad.png Binary file app/src/main/res/drawable-mdpi/emo_im_mad.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_money_mouth.png Binary file app/src/main/res/drawable-mdpi/emo_im_money_mouth.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_pokerface.png Binary file app/src/main/res/drawable-mdpi/emo_im_pokerface.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_sad.png Binary file app/src/main/res/drawable-mdpi/emo_im_sad.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_smirk.png Binary file app/src/main/res/drawable-mdpi/emo_im_smirk.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_surprised.png Binary file app/src/main/res/drawable-mdpi/emo_im_surprised.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_tongue_sticking_out.png Binary file app/src/main/res/drawable-mdpi/emo_im_tongue_sticking_out.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_undecided.png Binary file app/src/main/res/drawable-mdpi/emo_im_undecided.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_winking.png Binary file app/src/main/res/drawable-mdpi/emo_im_winking.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_wtf.png Binary file app/src/main/res/drawable-mdpi/emo_im_wtf.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable-mdpi/emo_im_yelling.png Binary file app/src/main/res/drawable-mdpi/emo_im_yelling.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/avatar_status.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/drawable/avatar_status.xml Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,16 @@ + + + + + + + + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/beem_launcher_icon_silver.png Binary file app/src/main/res/drawable/beem_launcher_icon_silver.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/beem_status_icon.png Binary file app/src/main/res/drawable/beem_status_icon.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/beem_status_icon_available.png Binary file app/src/main/res/drawable/beem_status_icon_available.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/beem_status_icon_away.png Binary file app/src/main/res/drawable/beem_status_icon_away.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/beem_status_icon_busy.png Binary file app/src/main/res/drawable/beem_status_icon_busy.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/beem_status_icon_gray.png Binary file app/src/main/res/drawable/beem_status_icon_gray.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/bottombar.png Binary file app/src/main/res/drawable/bottombar.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/button_indicator_next.png Binary file app/src/main/res/drawable/button_indicator_next.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/button_indicator_prev.png Binary file app/src/main/res/drawable/button_indicator_prev.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/ic_menu_add.png Binary file app/src/main/res/drawable/ic_menu_add.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/ic_menu_blocked_user.png Binary file app/src/main/res/drawable/ic_menu_blocked_user.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/ic_menu_chat_dashboard.png Binary file app/src/main/res/drawable/ic_menu_chat_dashboard.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/ic_menu_close_clear_cancel.png Binary file app/src/main/res/drawable/ic_menu_close_clear_cancel.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/ic_menu_end_conversation.png Binary file app/src/main/res/drawable/ic_menu_end_conversation.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/ic_menu_friendslist.png Binary file app/src/main/res/drawable/ic_menu_friendslist.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/ic_menu_invite.png Binary file app/src/main/res/drawable/ic_menu_invite.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/ic_menu_login.png Binary file app/src/main/res/drawable/ic_menu_login.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/ic_menu_manage.png Binary file app/src/main/res/drawable/ic_menu_manage.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/icon.png Binary file app/src/main/res/drawable/icon.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/logo.png Binary file app/src/main/res/drawable/logo.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/logo_encryption.png Binary file app/src/main/res/drawable/logo_encryption.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/not_in_the_roster.png Binary file app/src/main/res/drawable/not_in_the_roster.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/scrollbar_vertical_thumb.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/drawable/scrollbar_vertical_thumb.xml Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,21 @@ + + + + + + + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/scrollbar_vertical_track.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/drawable/scrollbar_vertical_track.xml Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,21 @@ + + + + + + + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/shape_border_green.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/drawable/shape_border_green.xml Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,7 @@ + + + + + + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/status_available.png Binary file app/src/main/res/drawable/status_available.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/status_away.png Binary file app/src/main/res/drawable/status_away.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/status_blocked.png Binary file app/src/main/res/drawable/status_blocked.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/status_dnd.png Binary file app/src/main/res/drawable/status_dnd.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/status_error.png Binary file app/src/main/res/drawable/status_error.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/status_icon.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/drawable/status_icon.xml Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,13 @@ + + + + + + + + + diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/status_idle.png Binary file app/src/main/res/drawable/status_idle.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/status_invisible.png Binary file app/src/main/res/drawable/status_invisible.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/status_new_message.png Binary file app/src/main/res/drawable/status_new_message.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/status_offline.png Binary file app/src/main/res/drawable/status_offline.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/status_requested.png Binary file app/src/main/res/drawable/status_requested.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/drawable/status_typing.png Binary file app/src/main/res/drawable/status_typing.png has changed diff -r 7d6f2526244a -r 197a85a35cba app/src/main/res/layout-v11/create_account.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/res/layout-v11/create_account.xml Sun Mar 15 18:03:03 2015 +0100 @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + +