mirror of
https://github.com/moparisthebest/Conversations
synced 2024-11-23 17:22:17 -05:00
Use Gradle build system
This commit is contained in:
commit
64bdd7e731
41
.gitignore
vendored
Normal file
41
.gitignore
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
.classpath
|
||||
*.swp
|
||||
.settings
|
||||
|
||||
# https://github.com/github/gitignore/blob/master/Gradle.gitignore
|
||||
.gradle/
|
||||
gradle/
|
||||
build/
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
# https://github.com/github/gitignore/blob/master/Android.gitignore
|
||||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# Files for the Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
*.iml
|
||||
.idea
|
||||
|
||||
import-summary.txt
|
||||
|
||||
*.jar
|
9
.gitmodules
vendored
Normal file
9
.gitmodules
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
[submodule "minidns"]
|
||||
path = minidns
|
||||
url = https://github.com/rtreffer/minidns.git
|
||||
[submodule "openpgpapilib"]
|
||||
path = openpgpapilib
|
||||
url = https://github.com/open-keychain/openpgp-api-lib.git
|
||||
[submodule "memorizingTrustManager"]
|
||||
path = memorizingTrustManager
|
||||
url = https://github.com/iNPUTmice/MemorizingTrustManager.git
|
16
build.gradle
Normal file
16
build.gradle
Normal file
@ -0,0 +1,16 @@
|
||||
// Top-level build file where you can add configuration options common to all
|
||||
// sub-projects/modules.
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.12.2'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
}
|
28
conversations/build.gradle
Normal file
28
conversations/build.gradle
Normal file
@ -0,0 +1,28 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 19
|
||||
buildToolsVersion "20.0.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "eu.siacs.conversations"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 19
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
runProguard false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':minidns')
|
||||
compile project(':openpgpapilib')
|
||||
compile project(':memorizingTrustManager')
|
||||
compile files('libs/android-support-v13.jar')
|
||||
compile files('libs/bcprov-jdk15on-150.jar')
|
||||
compile files('libs/otr4j-0.10.jar')
|
||||
}
|
124
conversations/src/main/AndroidManifest.xml
Normal file
124
conversations/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="eu.siacs.conversations"
|
||||
android:versionCode="32"
|
||||
android:versionName="0.8-alpha" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="14"
|
||||
android:targetSdkVersion="19" />
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_PROFILE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
tools:replace="android:label"
|
||||
android:theme="@style/ConversationsTheme" >
|
||||
<service android:name="eu.siacs.conversations.services.XmppConnectionService" />
|
||||
|
||||
<receiver android:name="eu.siacs.conversations.services.EventReceiver" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
|
||||
<action android:name="android.intent.action.ACTION_SHUTDOWN" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name="eu.siacs.conversations.ui.ConversationActivity"
|
||||
android:label="@string/title_activity_conversations"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="stateHidden" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="eu.siacs.conversations.ui.StartConversationActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/title_activity_start_conversation"
|
||||
android:logo="@drawable/ic_activity" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SENDTO" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:scheme="imto" />
|
||||
<data android:host="jabber" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="xmpp" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="eu.siacs.conversations.ui.SettingsActivity"
|
||||
android:label="@string/title_activity_settings" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name="eu.siacs.conversations.ui.ChooseContactActivity"
|
||||
android:label="@string/title_activity_choose_contact" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name="eu.siacs.conversations.ui.ManageAccountActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/title_activity_manage_accounts" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name="eu.siacs.conversations.ui.EditAccountActivity"
|
||||
android:windowSoftInputMode="stateHidden|adjustResize" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name="eu.siacs.conversations.ui.ConferenceDetailsActivity"
|
||||
android:label="@string/title_activity_conference_details"
|
||||
android:windowSoftInputMode="stateHidden" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name="eu.siacs.conversations.ui.ContactDetailsActivity"
|
||||
android:label="@string/title_activity_contact_details"
|
||||
android:windowSoftInputMode="stateHidden" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name="eu.siacs.conversations.ui.PublishProfilePictureActivity"
|
||||
android:label="@string/mgmt_account_publish_avatar"
|
||||
android:windowSoftInputMode="stateHidden" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name="eu.siacs.conversations.ui.ShareWithActivity"
|
||||
android:label="@string/title_activity_conversations" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="de.duenndns.ssl.MemorizingActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,25 @@
|
||||
package eu.siacs.conversations;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
public final class Config {
|
||||
|
||||
public static final String LOGTAG = "conversations";
|
||||
|
||||
public static final int PING_MAX_INTERVAL = 300;
|
||||
public static final int PING_MIN_INTERVAL = 30;
|
||||
public static final int PING_TIMEOUT = 10;
|
||||
public static final int CONNECT_TIMEOUT = 90;
|
||||
public static final int CARBON_GRACE_PERIOD = 60;
|
||||
|
||||
public static final int AVATAR_SIZE = 192;
|
||||
public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP;
|
||||
|
||||
public static final int MESSAGE_MERGE_WINDOW = 20;
|
||||
|
||||
public static final boolean PARSE_EMOTICONS = false;
|
||||
|
||||
private Config() {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
package eu.siacs.conversations.crypto;
|
||||
|
||||
import java.math.BigInteger;
|
||||
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.DSAPrivateKeySpec;
|
||||
import java.security.spec.DSAPublicKeySpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||
|
||||
import net.java.otr4j.OtrEngineHost;
|
||||
import net.java.otr4j.OtrException;
|
||||
import net.java.otr4j.OtrPolicy;
|
||||
import net.java.otr4j.OtrPolicyImpl;
|
||||
import net.java.otr4j.session.InstanceTag;
|
||||
import net.java.otr4j.session.SessionID;
|
||||
|
||||
public class OtrEngine implements OtrEngineHost {
|
||||
|
||||
private Account account;
|
||||
private OtrPolicy otrPolicy;
|
||||
private KeyPair keyPair;
|
||||
private XmppConnectionService mXmppConnectionService;
|
||||
|
||||
public OtrEngine(XmppConnectionService service, Account account) {
|
||||
this.account = account;
|
||||
this.otrPolicy = new OtrPolicyImpl();
|
||||
this.otrPolicy.setAllowV1(false);
|
||||
this.otrPolicy.setAllowV2(true);
|
||||
this.otrPolicy.setAllowV3(true);
|
||||
this.keyPair = loadKey(account.getKeys());
|
||||
this.mXmppConnectionService = service;
|
||||
}
|
||||
|
||||
private KeyPair loadKey(JSONObject keys) {
|
||||
if (keys == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
BigInteger x = new BigInteger(keys.getString("otr_x"), 16);
|
||||
BigInteger y = new BigInteger(keys.getString("otr_y"), 16);
|
||||
BigInteger p = new BigInteger(keys.getString("otr_p"), 16);
|
||||
BigInteger q = new BigInteger(keys.getString("otr_q"), 16);
|
||||
BigInteger g = new BigInteger(keys.getString("otr_g"), 16);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
|
||||
DSAPublicKeySpec pubKeySpec = new DSAPublicKeySpec(y, p, q, g);
|
||||
DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(x, p, q, g);
|
||||
PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
|
||||
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
|
||||
return new KeyPair(publicKey, privateKey);
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
} catch (InvalidKeySpecException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void saveKey() {
|
||||
PublicKey publicKey = keyPair.getPublic();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
KeyFactory keyFactory;
|
||||
try {
|
||||
keyFactory = KeyFactory.getInstance("DSA");
|
||||
DSAPrivateKeySpec privateKeySpec = keyFactory.getKeySpec(
|
||||
privateKey, DSAPrivateKeySpec.class);
|
||||
DSAPublicKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey,
|
||||
DSAPublicKeySpec.class);
|
||||
this.account.setKey("otr_x", privateKeySpec.getX().toString(16));
|
||||
this.account.setKey("otr_g", privateKeySpec.getG().toString(16));
|
||||
this.account.setKey("otr_p", privateKeySpec.getP().toString(16));
|
||||
this.account.setKey("otr_q", privateKeySpec.getQ().toString(16));
|
||||
this.account.setKey("otr_y", publicKeySpec.getY().toString(16));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvalidKeySpecException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void askForSecret(SessionID arg0, InstanceTag arg1, String arg2) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishedSessionMessage(SessionID arg0, String arg1)
|
||||
throws OtrException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFallbackMessage(SessionID arg0) {
|
||||
return "I would like to start a private (OTR encrypted) conversation but your client doesn’t seem to support that";
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getLocalFingerprintRaw(SessionID arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public PublicKey getPublicKey() {
|
||||
if (this.keyPair == null) {
|
||||
return null;
|
||||
}
|
||||
return this.keyPair.getPublic();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair getLocalKeyPair(SessionID arg0) throws OtrException {
|
||||
if (this.keyPair == null) {
|
||||
KeyPairGenerator kg;
|
||||
try {
|
||||
kg = KeyPairGenerator.getInstance("DSA");
|
||||
this.keyPair = kg.genKeyPair();
|
||||
this.saveKey();
|
||||
mXmppConnectionService.databaseBackend.updateAccount(account);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.d(Config.LOGTAG,
|
||||
"error generating key pair " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return this.keyPair;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReplyForUnreadableMessage(SessionID arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OtrPolicy getSessionPolicy(SessionID arg0) {
|
||||
return otrPolicy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectMessage(SessionID session, String body)
|
||||
throws OtrException {
|
||||
MessagePacket packet = new MessagePacket();
|
||||
packet.setFrom(account.getFullJid());
|
||||
if (session.getUserID().isEmpty()) {
|
||||
packet.setTo(session.getAccountID());
|
||||
} else {
|
||||
packet.setTo(session.getAccountID() + "/" + session.getUserID());
|
||||
}
|
||||
packet.setBody(body);
|
||||
packet.addChild("private", "urn:xmpp:carbons:2");
|
||||
packet.addChild("no-copy", "urn:xmpp:hints");
|
||||
packet.setType(MessagePacket.TYPE_CHAT);
|
||||
account.getXmppConnection().sendMessagePacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageFromAnotherInstanceReceived(SessionID id) {
|
||||
Log.d(Config.LOGTAG,
|
||||
"unreadable message received from " + id.getAccountID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void multipleInstancesDetected(SessionID arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requireEncryptedMessage(SessionID arg0, String arg1)
|
||||
throws OtrException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showError(SessionID arg0, String arg1) throws OtrException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void smpAborted(SessionID arg0) throws OtrException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void smpError(SessionID arg0, int arg1, boolean arg2)
|
||||
throws OtrException {
|
||||
throw new OtrException(new Exception("smp error"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unencryptedMessageReceived(SessionID arg0, String arg1)
|
||||
throws OtrException {
|
||||
throw new OtrException(new Exception("unencrypted message received"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unreadableMessageReceived(SessionID arg0) throws OtrException {
|
||||
throw new OtrException(new Exception("unreadable message received"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unverify(SessionID arg0, String arg1) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(SessionID arg0, String arg1, boolean arg2) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,385 @@
|
||||
package eu.siacs.conversations.crypto;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
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 org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.ui.UiCallback;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.util.Log;
|
||||
|
||||
public class PgpEngine {
|
||||
private OpenPgpApi api;
|
||||
private XmppConnectionService mXmppConnectionService;
|
||||
|
||||
public PgpEngine(OpenPgpApi api, XmppConnectionService service) {
|
||||
this.api = api;
|
||||
this.mXmppConnectionService = service;
|
||||
}
|
||||
|
||||
public void decrypt(final Message message,
|
||||
final UiCallback<Message> callback) {
|
||||
Log.d(Config.LOGTAG, "decrypting message " + message.getUuid());
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
|
||||
.getConversation().getAccount().getJid());
|
||||
if (message.getType() == Message.TYPE_TEXT) {
|
||||
InputStream is = new ByteArrayInputStream(message.getBody()
|
||||
.getBytes());
|
||||
final OutputStream os = new ByteArrayOutputStream();
|
||||
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
|
||||
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
|
||||
OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||
try {
|
||||
os.flush();
|
||||
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||
message.setBody(os.toString());
|
||||
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
||||
callback.success(message);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
callback.error(R.string.openpgp_error, message);
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
callback.userInputRequried((PendingIntent) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
|
||||
message);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
OpenPgpError error = result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
|
||||
Log.d(Config.LOGTAG,
|
||||
"openpgp error: " + error.getMessage());
|
||||
callback.error(R.string.openpgp_error, message);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (message.getType() == Message.TYPE_IMAGE) {
|
||||
try {
|
||||
final DownloadableFile inputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getFile(message, false);
|
||||
final DownloadableFile outputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getFile(message, true);
|
||||
outputFile.createNewFile();
|
||||
InputStream is = new FileInputStream(inputFile);
|
||||
OutputStream os = new FileOutputStream(outputFile);
|
||||
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
|
||||
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
|
||||
OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(
|
||||
outputFile.getAbsolutePath(), options);
|
||||
int imageHeight = options.outHeight;
|
||||
int imageWidth = options.outWidth;
|
||||
message.setBody(Long.toString(outputFile.getSize())
|
||||
+ ',' + imageWidth + ',' + imageHeight);
|
||||
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
||||
PgpEngine.this.mXmppConnectionService
|
||||
.updateMessage(message);
|
||||
;
|
||||
callback.success(message);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
callback.userInputRequried(
|
||||
(PendingIntent) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
|
||||
message);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
callback.error(R.string.openpgp_error, message);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (FileNotFoundException e) {
|
||||
callback.error(R.string.error_decrypting_file, message);
|
||||
} catch (IOException e) {
|
||||
callback.error(R.string.error_decrypting_file, message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void encrypt(final Message message,
|
||||
final UiCallback<Message> callback) {
|
||||
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_ENCRYPT);
|
||||
if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
|
||||
long[] keys = { message.getConversation().getContact()
|
||||
.getPgpKeyId() };
|
||||
params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
|
||||
} else {
|
||||
params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, message.getConversation()
|
||||
.getMucOptions().getPgpKeyIds());
|
||||
}
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
|
||||
.getConversation().getAccount().getJid());
|
||||
|
||||
if (message.getType() == Message.TYPE_TEXT) {
|
||||
params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
|
||||
InputStream is = new ByteArrayInputStream(message.getBody()
|
||||
.getBytes());
|
||||
final OutputStream os = new ByteArrayOutputStream();
|
||||
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
|
||||
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
|
||||
OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||
try {
|
||||
os.flush();
|
||||
StringBuilder encryptedMessageBody = new StringBuilder();
|
||||
String[] lines = os.toString().split("\n");
|
||||
for (int i = 2; i < lines.length - 1; ++i) {
|
||||
if (!lines[i].contains("Version")) {
|
||||
encryptedMessageBody.append(lines[i].trim());
|
||||
}
|
||||
}
|
||||
message.setEncryptedBody(encryptedMessageBody
|
||||
.toString());
|
||||
callback.success(message);
|
||||
} catch (IOException e) {
|
||||
callback.error(R.string.openpgp_error, message);
|
||||
}
|
||||
|
||||
break;
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
callback.userInputRequried((PendingIntent) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
|
||||
message);
|
||||
break;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
callback.error(R.string.openpgp_error, message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (message.getType() == Message.TYPE_IMAGE) {
|
||||
try {
|
||||
DownloadableFile inputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getFile(message, true);
|
||||
DownloadableFile outputFile = this.mXmppConnectionService
|
||||
.getFileBackend().getFile(message, false);
|
||||
outputFile.createNewFile();
|
||||
InputStream is = new FileInputStream(inputFile);
|
||||
OutputStream os = new FileOutputStream(outputFile);
|
||||
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
|
||||
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
|
||||
OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||
callback.success(message);
|
||||
break;
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
callback.userInputRequried(
|
||||
(PendingIntent) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
|
||||
message);
|
||||
break;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
callback.error(R.string.openpgp_error, message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.d(Config.LOGTAG, "file not found: " + e.getMessage());
|
||||
} catch (IOException e) {
|
||||
Log.d(Config.LOGTAG, "io exception during file encrypt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long fetchKeyId(Account account, String status, String signature) {
|
||||
if ((signature == null) || (api == null)) {
|
||||
return 0;
|
||||
}
|
||||
if (status == null) {
|
||||
status = "";
|
||||
}
|
||||
StringBuilder pgpSig = new StringBuilder();
|
||||
pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
|
||||
pgpSig.append('\n');
|
||||
pgpSig.append('\n');
|
||||
pgpSig.append(status);
|
||||
pgpSig.append('\n');
|
||||
pgpSig.append("-----BEGIN PGP SIGNATURE-----");
|
||||
pgpSig.append('\n');
|
||||
pgpSig.append('\n');
|
||||
pgpSig.append(signature.replace("\n", "").trim());
|
||||
pgpSig.append('\n');
|
||||
pgpSig.append("-----END PGP SIGNATURE-----");
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||
params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
|
||||
InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
Intent result = api.executeApi(params, is, os);
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
|
||||
OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||
OpenPgpSignatureResult sigResult = result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
|
||||
if (sigResult != null) {
|
||||
return sigResult.getKeyId();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
return 0;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
Log.d(Config.LOGTAG,
|
||||
"openpgp error: "
|
||||
+ ((OpenPgpError) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_ERROR))
|
||||
.getMessage());
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void generateSignature(final Account account, String status,
|
||||
final UiCallback<Account> callback) {
|
||||
Intent params = new Intent();
|
||||
params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
params.setAction(OpenPgpApi.ACTION_SIGN);
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
|
||||
InputStream is = new ByteArrayInputStream(status.getBytes());
|
||||
final OutputStream os = new ByteArrayOutputStream();
|
||||
api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
|
||||
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||
StringBuilder signatureBuilder = new StringBuilder();
|
||||
try {
|
||||
os.flush();
|
||||
String[] lines = os.toString().split("\n");
|
||||
boolean sig = false;
|
||||
for (String line : lines) {
|
||||
if (sig) {
|
||||
if (line.contains("END PGP SIGNATURE")) {
|
||||
sig = false;
|
||||
} else {
|
||||
if (!line.contains("Version")) {
|
||||
signatureBuilder.append(line.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (line.contains("BEGIN PGP SIGNATURE")) {
|
||||
sig = true;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
callback.error(R.string.openpgp_error, account);
|
||||
return;
|
||||
}
|
||||
account.setKey("pgp_signature", signatureBuilder.toString());
|
||||
callback.success(account);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
callback.userInputRequried((PendingIntent) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
|
||||
account);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
callback.error(R.string.openpgp_error, account);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_GET_KEY);
|
||||
params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
|
||||
.getJid());
|
||||
api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
|
||||
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS:
|
||||
callback.success(contact);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||
callback.userInputRequried((PendingIntent) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
|
||||
contact);
|
||||
return;
|
||||
case OpenPgpApi.RESULT_CODE_ERROR:
|
||||
callback.error(R.string.openpgp_error, contact);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public PendingIntent getIntentForKey(Contact contact) {
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_GET_KEY);
|
||||
params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
|
||||
.getJid());
|
||||
Intent result = api.executeApi(params, null, null);
|
||||
return (PendingIntent) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
}
|
||||
|
||||
public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
|
||||
Intent params = new Intent();
|
||||
params.setAction(OpenPgpApi.ACTION_GET_KEY);
|
||||
params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
|
||||
params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
|
||||
Intent result = api.executeApi(params, null, null);
|
||||
return (PendingIntent) result
|
||||
.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
import android.content.ContentValues;
|
||||
|
||||
public abstract class AbstractEntity {
|
||||
|
||||
public static final String UUID = "uuid";
|
||||
|
||||
protected String uuid;
|
||||
|
||||
public String getUuid() {
|
||||
return this.uuid;
|
||||
}
|
||||
|
||||
public abstract ContentValues getContentValues();
|
||||
|
||||
public boolean equals(AbstractEntity entity) {
|
||||
return this.getUuid().equals(entity.getUuid());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,399 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
import java.security.interfaces.DSAPublicKey;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
|
||||
import net.java.otr4j.crypto.OtrCryptoException;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.crypto.OtrEngine;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xmpp.XmppConnection;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.os.SystemClock;
|
||||
|
||||
public class Account extends AbstractEntity {
|
||||
|
||||
public static final String TABLENAME = "accounts";
|
||||
|
||||
public static final String USERNAME = "username";
|
||||
public static final String SERVER = "server";
|
||||
public static final String PASSWORD = "password";
|
||||
public static final String OPTIONS = "options";
|
||||
public static final String ROSTERVERSION = "rosterversion";
|
||||
public static final String KEYS = "keys";
|
||||
public static final String AVATAR = "avatar";
|
||||
|
||||
public static final int OPTION_USETLS = 0;
|
||||
public static final int OPTION_DISABLED = 1;
|
||||
public static final int OPTION_REGISTER = 2;
|
||||
public static final int OPTION_USECOMPRESSION = 3;
|
||||
|
||||
public static final int STATUS_CONNECTING = 0;
|
||||
public static final int STATUS_DISABLED = -2;
|
||||
public static final int STATUS_OFFLINE = -1;
|
||||
public static final int STATUS_ONLINE = 1;
|
||||
public static final int STATUS_NO_INTERNET = 2;
|
||||
public static final int STATUS_UNAUTHORIZED = 3;
|
||||
public static final int STATUS_SERVER_NOT_FOUND = 5;
|
||||
|
||||
public static final int STATUS_REGISTRATION_FAILED = 7;
|
||||
public static final int STATUS_REGISTRATION_CONFLICT = 8;
|
||||
public static final int STATUS_REGISTRATION_SUCCESSFULL = 9;
|
||||
public static final int STATUS_REGISTRATION_NOT_SUPPORTED = 10;
|
||||
|
||||
protected String username;
|
||||
protected String server;
|
||||
protected String password;
|
||||
protected int options = 0;
|
||||
protected String rosterVersion;
|
||||
protected String resource = "mobile";
|
||||
protected int status = -1;
|
||||
protected JSONObject keys = new JSONObject();
|
||||
protected String avatar;
|
||||
|
||||
protected boolean online = false;
|
||||
|
||||
private OtrEngine otrEngine = null;
|
||||
private XmppConnection xmppConnection = null;
|
||||
private Presences presences = new Presences();
|
||||
private long mEndGracePeriod = 0L;
|
||||
private String otrFingerprint;
|
||||
private Roster roster = null;
|
||||
|
||||
private List<Bookmark> bookmarks = new CopyOnWriteArrayList<Bookmark>();
|
||||
public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<Conversation>();
|
||||
public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<Conversation>();
|
||||
|
||||
public Account() {
|
||||
this.uuid = "0";
|
||||
}
|
||||
|
||||
public Account(String username, String server, String password) {
|
||||
this(java.util.UUID.randomUUID().toString(), username, server,
|
||||
password, 0, null, "", null);
|
||||
}
|
||||
|
||||
public Account(String uuid, String username, String server,
|
||||
String password, int options, String rosterVersion, String keys,
|
||||
String avatar) {
|
||||
this.uuid = uuid;
|
||||
this.username = username;
|
||||
this.server = server;
|
||||
this.password = password;
|
||||
this.options = options;
|
||||
this.rosterVersion = rosterVersion;
|
||||
try {
|
||||
this.keys = new JSONObject(keys);
|
||||
} catch (JSONException e) {
|
||||
|
||||
}
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
public boolean isOptionSet(int option) {
|
||||
return ((options & (1 << option)) != 0);
|
||||
}
|
||||
|
||||
public void setOption(int option, boolean value) {
|
||||
if (value) {
|
||||
this.options |= 1 << option;
|
||||
} else {
|
||||
this.options &= ~(1 << option);
|
||||
}
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public void setServer(String server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
if (isOptionSet(OPTION_DISABLED)) {
|
||||
return STATUS_DISABLED;
|
||||
} else {
|
||||
return this.status;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean errorStatus() {
|
||||
int s = getStatus();
|
||||
return (s == STATUS_REGISTRATION_FAILED
|
||||
|| s == STATUS_REGISTRATION_CONFLICT
|
||||
|| s == STATUS_REGISTRATION_NOT_SUPPORTED
|
||||
|| s == STATUS_SERVER_NOT_FOUND || s == STATUS_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
public boolean hasErrorStatus() {
|
||||
if (getXmppConnection() == null) {
|
||||
return false;
|
||||
} else {
|
||||
return getStatus() > STATUS_NO_INTERNET
|
||||
&& (getXmppConnection().getAttempt() >= 2);
|
||||
}
|
||||
}
|
||||
|
||||
public void setResource(String resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public String getResource() {
|
||||
return this.resource;
|
||||
}
|
||||
|
||||
public String getJid() {
|
||||
return username.toLowerCase(Locale.getDefault()) + "@"
|
||||
+ server.toLowerCase(Locale.getDefault());
|
||||
}
|
||||
|
||||
public JSONObject getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
public String getSSLFingerprint() {
|
||||
if (keys.has("ssl_cert")) {
|
||||
try {
|
||||
return keys.getString("ssl_cert");
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSSLCertFingerprint(String fingerprint) {
|
||||
this.setKey("ssl_cert", fingerprint);
|
||||
}
|
||||
|
||||
public boolean setKey(String keyName, String keyValue) {
|
||||
try {
|
||||
this.keys.put(keyName, keyValue);
|
||||
return true;
|
||||
} catch (JSONException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(UUID, uuid);
|
||||
values.put(USERNAME, username);
|
||||
values.put(SERVER, server);
|
||||
values.put(PASSWORD, password);
|
||||
values.put(OPTIONS, options);
|
||||
values.put(KEYS, this.keys.toString());
|
||||
values.put(ROSTERVERSION, rosterVersion);
|
||||
values.put(AVATAR, avatar);
|
||||
return values;
|
||||
}
|
||||
|
||||
public static Account fromCursor(Cursor cursor) {
|
||||
return new Account(cursor.getString(cursor.getColumnIndex(UUID)),
|
||||
cursor.getString(cursor.getColumnIndex(USERNAME)),
|
||||
cursor.getString(cursor.getColumnIndex(SERVER)),
|
||||
cursor.getString(cursor.getColumnIndex(PASSWORD)),
|
||||
cursor.getInt(cursor.getColumnIndex(OPTIONS)),
|
||||
cursor.getString(cursor.getColumnIndex(ROSTERVERSION)),
|
||||
cursor.getString(cursor.getColumnIndex(KEYS)),
|
||||
cursor.getString(cursor.getColumnIndex(AVATAR)));
|
||||
}
|
||||
|
||||
public OtrEngine getOtrEngine(XmppConnectionService context) {
|
||||
if (otrEngine == null) {
|
||||
otrEngine = new OtrEngine(context, this);
|
||||
}
|
||||
return this.otrEngine;
|
||||
}
|
||||
|
||||
public XmppConnection getXmppConnection() {
|
||||
return this.xmppConnection;
|
||||
}
|
||||
|
||||
public void setXmppConnection(XmppConnection connection) {
|
||||
this.xmppConnection = connection;
|
||||
}
|
||||
|
||||
public String getFullJid() {
|
||||
return this.getJid() + "/" + this.resource;
|
||||
}
|
||||
|
||||
public String getOtrFingerprint() {
|
||||
if (this.otrFingerprint == null) {
|
||||
try {
|
||||
DSAPublicKey pubkey = (DSAPublicKey) this.otrEngine
|
||||
.getPublicKey();
|
||||
if (pubkey == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder builder = new StringBuilder(
|
||||
new OtrCryptoEngineImpl().getFingerprint(pubkey));
|
||||
builder.insert(8, " ");
|
||||
builder.insert(17, " ");
|
||||
builder.insert(26, " ");
|
||||
builder.insert(35, " ");
|
||||
this.otrFingerprint = builder.toString();
|
||||
} catch (OtrCryptoException e) {
|
||||
|
||||
}
|
||||
}
|
||||
return this.otrFingerprint;
|
||||
}
|
||||
|
||||
public String getRosterVersion() {
|
||||
if (this.rosterVersion == null) {
|
||||
return "";
|
||||
} else {
|
||||
return this.rosterVersion;
|
||||
}
|
||||
}
|
||||
|
||||
public void setRosterVersion(String version) {
|
||||
this.rosterVersion = version;
|
||||
}
|
||||
|
||||
public String getOtrFingerprint(XmppConnectionService service) {
|
||||
this.getOtrEngine(service);
|
||||
return this.getOtrFingerprint();
|
||||
}
|
||||
|
||||
public void updatePresence(String resource, int status) {
|
||||
this.presences.updatePresence(resource, status);
|
||||
}
|
||||
|
||||
public void removePresence(String resource) {
|
||||
this.presences.removePresence(resource);
|
||||
}
|
||||
|
||||
public void clearPresences() {
|
||||
this.presences = new Presences();
|
||||
}
|
||||
|
||||
public int countPresences() {
|
||||
return this.presences.size();
|
||||
}
|
||||
|
||||
public String getPgpSignature() {
|
||||
if (keys.has("pgp_signature")) {
|
||||
try {
|
||||
return keys.getString("pgp_signature");
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Roster getRoster() {
|
||||
if (this.roster == null) {
|
||||
this.roster = new Roster(this);
|
||||
}
|
||||
return this.roster;
|
||||
}
|
||||
|
||||
public void setBookmarks(List<Bookmark> bookmarks) {
|
||||
this.bookmarks = bookmarks;
|
||||
}
|
||||
|
||||
public List<Bookmark> getBookmarks() {
|
||||
return this.bookmarks;
|
||||
}
|
||||
|
||||
public boolean hasBookmarkFor(String conferenceJid) {
|
||||
for (Bookmark bmark : this.bookmarks) {
|
||||
if (bmark.getJid().equals(conferenceJid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean setAvatar(String filename) {
|
||||
if (this.avatar != null && this.avatar.equals(filename)) {
|
||||
return false;
|
||||
} else {
|
||||
this.avatar = filename;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public String getAvatar() {
|
||||
return this.avatar;
|
||||
}
|
||||
|
||||
public int getReadableStatusId() {
|
||||
switch (getStatus()) {
|
||||
|
||||
case Account.STATUS_DISABLED:
|
||||
return R.string.account_status_disabled;
|
||||
case Account.STATUS_ONLINE:
|
||||
return R.string.account_status_online;
|
||||
case Account.STATUS_CONNECTING:
|
||||
return R.string.account_status_connecting;
|
||||
case Account.STATUS_OFFLINE:
|
||||
return R.string.account_status_offline;
|
||||
case Account.STATUS_UNAUTHORIZED:
|
||||
return R.string.account_status_unauthorized;
|
||||
case Account.STATUS_SERVER_NOT_FOUND:
|
||||
return R.string.account_status_not_found;
|
||||
case Account.STATUS_NO_INTERNET:
|
||||
return R.string.account_status_no_internet;
|
||||
case Account.STATUS_REGISTRATION_FAILED:
|
||||
return R.string.account_status_regis_fail;
|
||||
case Account.STATUS_REGISTRATION_CONFLICT:
|
||||
return R.string.account_status_regis_conflict;
|
||||
case Account.STATUS_REGISTRATION_SUCCESSFULL:
|
||||
return R.string.account_status_regis_success;
|
||||
case Account.STATUS_REGISTRATION_NOT_SUPPORTED:
|
||||
return R.string.account_status_regis_not_sup;
|
||||
default:
|
||||
return R.string.account_status_unknown;
|
||||
}
|
||||
}
|
||||
|
||||
public void activateGracePeriod() {
|
||||
this.mEndGracePeriod = SystemClock.elapsedRealtime()
|
||||
+ (Config.CARBON_GRACE_PERIOD * 1000);
|
||||
}
|
||||
|
||||
public void deactivateGracePeriod() {
|
||||
this.mEndGracePeriod = 0L;
|
||||
}
|
||||
|
||||
public boolean inGracePeriod() {
|
||||
return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
|
||||
}
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
|
||||
public class Bookmark extends Element implements ListItem {
|
||||
|
||||
private Account account;
|
||||
private Conversation mJoinedConversation;
|
||||
|
||||
public Bookmark(Account account, String jid) {
|
||||
super("conference");
|
||||
this.setAttribute("jid", jid);
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
private Bookmark(Account account) {
|
||||
super("conference");
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public static Bookmark parse(Element element, Account account) {
|
||||
Bookmark bookmark = new Bookmark(account);
|
||||
bookmark.setAttributes(element.getAttributes());
|
||||
bookmark.setChildren(element.getChildren());
|
||||
return bookmark;
|
||||
}
|
||||
|
||||
public void setAutojoin(boolean autojoin) {
|
||||
if (autojoin) {
|
||||
this.setAttribute("autojoin", "true");
|
||||
} else {
|
||||
this.setAttribute("autojoin", "false");
|
||||
}
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setNick(String nick) {
|
||||
Element element = this.findChild("nick");
|
||||
if (element == null) {
|
||||
element = this.addChild("nick");
|
||||
}
|
||||
element.setContent(nick);
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
Element element = this.findChild("password");
|
||||
if (element != null) {
|
||||
element.setContent(password);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ListItem another) {
|
||||
return this.getDisplayName().compareToIgnoreCase(
|
||||
another.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
if (this.mJoinedConversation != null
|
||||
&& (this.mJoinedConversation.getMucOptions().getSubject() != null)) {
|
||||
return this.mJoinedConversation.getMucOptions().getSubject();
|
||||
} else if (getName() != null) {
|
||||
return getName();
|
||||
} else {
|
||||
return this.getJid().split("@")[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJid() {
|
||||
String jid = this.getAttribute("jid");
|
||||
if (jid != null) {
|
||||
return jid.toLowerCase(Locale.US);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getNick() {
|
||||
Element nick = this.findChild("nick");
|
||||
if (nick != null) {
|
||||
return nick.getContent();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean autojoin() {
|
||||
String autojoin = this.getAttribute("autojoin");
|
||||
return (autojoin != null && (autojoin.equalsIgnoreCase("true") || autojoin
|
||||
.equalsIgnoreCase("1")));
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
Element password = this.findChild("password");
|
||||
if (password != null) {
|
||||
return password.getContent();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean match(String needle) {
|
||||
return needle == null
|
||||
|| getJid().contains(needle.toLowerCase(Locale.US))
|
||||
|| getDisplayName().toLowerCase(Locale.US).contains(
|
||||
needle.toLowerCase(Locale.US));
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return this.account;
|
||||
}
|
||||
|
||||
public void setConversation(Conversation conversation) {
|
||||
this.mJoinedConversation = conversation;
|
||||
}
|
||||
|
||||
public Conversation getConversation() {
|
||||
return this.mJoinedConversation;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.getAttribute("name");
|
||||
}
|
||||
|
||||
public void unregisterConversation() {
|
||||
if (this.mJoinedConversation != null) {
|
||||
this.mJoinedConversation.deregisterWithBookmark();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,367 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
public class Contact implements ListItem {
|
||||
public static final String TABLENAME = "contacts";
|
||||
|
||||
public static final String SYSTEMNAME = "systemname";
|
||||
public static final String SERVERNAME = "servername";
|
||||
public static final String JID = "jid";
|
||||
public static final String OPTIONS = "options";
|
||||
public static final String SYSTEMACCOUNT = "systemaccount";
|
||||
public static final String PHOTOURI = "photouri";
|
||||
public static final String KEYS = "pgpkey";
|
||||
public static final String ACCOUNT = "accountUuid";
|
||||
public static final String AVATAR = "avatar";
|
||||
|
||||
protected String accountUuid;
|
||||
protected String systemName;
|
||||
protected String serverName;
|
||||
protected String presenceName;
|
||||
protected String jid;
|
||||
protected int subscription = 0;
|
||||
protected String systemAccount;
|
||||
protected String photoUri;
|
||||
protected String avatar;
|
||||
protected JSONObject keys = new JSONObject();
|
||||
protected Presences presences = new Presences();
|
||||
|
||||
protected Account account;
|
||||
|
||||
protected boolean inRoster = true;
|
||||
|
||||
public Lastseen lastseen = new Lastseen();
|
||||
|
||||
public Contact(String account, String systemName, String serverName,
|
||||
String jid, int subscription, String photoUri,
|
||||
String systemAccount, String keys, String avatar) {
|
||||
this.accountUuid = account;
|
||||
this.systemName = systemName;
|
||||
this.serverName = serverName;
|
||||
this.jid = jid;
|
||||
this.subscription = subscription;
|
||||
this.photoUri = photoUri;
|
||||
this.systemAccount = systemAccount;
|
||||
if (keys == null) {
|
||||
keys = "";
|
||||
}
|
||||
try {
|
||||
this.keys = new JSONObject(keys);
|
||||
} catch (JSONException e) {
|
||||
this.keys = new JSONObject();
|
||||
}
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
public Contact(String jid) {
|
||||
this.jid = jid;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
if (this.systemName != null) {
|
||||
return this.systemName;
|
||||
} else if (this.serverName != null) {
|
||||
return this.serverName;
|
||||
} else if (this.presenceName != null) {
|
||||
return this.presenceName;
|
||||
} else {
|
||||
return this.jid.split("@")[0];
|
||||
}
|
||||
}
|
||||
|
||||
public String getProfilePhoto() {
|
||||
return this.photoUri;
|
||||
}
|
||||
|
||||
public String getJid() {
|
||||
return this.jid.toLowerCase(Locale.getDefault());
|
||||
}
|
||||
|
||||
public boolean match(String needle) {
|
||||
return needle == null
|
||||
|| jid.contains(needle.toLowerCase())
|
||||
|| getDisplayName().toLowerCase()
|
||||
.contains(needle.toLowerCase());
|
||||
}
|
||||
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(ACCOUNT, accountUuid);
|
||||
values.put(SYSTEMNAME, systemName);
|
||||
values.put(SERVERNAME, serverName);
|
||||
values.put(JID, jid);
|
||||
values.put(OPTIONS, subscription);
|
||||
values.put(SYSTEMACCOUNT, systemAccount);
|
||||
values.put(PHOTOURI, photoUri);
|
||||
values.put(KEYS, keys.toString());
|
||||
values.put(AVATAR, avatar);
|
||||
return values;
|
||||
}
|
||||
|
||||
public static Contact fromCursor(Cursor cursor) {
|
||||
return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)),
|
||||
cursor.getString(cursor.getColumnIndex(SYSTEMNAME)),
|
||||
cursor.getString(cursor.getColumnIndex(SERVERNAME)),
|
||||
cursor.getString(cursor.getColumnIndex(JID)),
|
||||
cursor.getInt(cursor.getColumnIndex(OPTIONS)),
|
||||
cursor.getString(cursor.getColumnIndex(PHOTOURI)),
|
||||
cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)),
|
||||
cursor.getString(cursor.getColumnIndex(KEYS)),
|
||||
cursor.getString(cursor.getColumnIndex(AVATAR)));
|
||||
}
|
||||
|
||||
public int getSubscription() {
|
||||
return this.subscription;
|
||||
}
|
||||
|
||||
public void setSystemAccount(String account) {
|
||||
this.systemAccount = account;
|
||||
}
|
||||
|
||||
public void setAccount(Account account) {
|
||||
this.account = account;
|
||||
this.accountUuid = account.getUuid();
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return this.account;
|
||||
}
|
||||
|
||||
public Presences getPresences() {
|
||||
return this.presences;
|
||||
}
|
||||
|
||||
public void updatePresence(String resource, int status) {
|
||||
this.presences.updatePresence(resource, status);
|
||||
}
|
||||
|
||||
public void removePresence(String resource) {
|
||||
this.presences.removePresence(resource);
|
||||
}
|
||||
|
||||
public void clearPresences() {
|
||||
this.presences.clearPresences();
|
||||
this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
|
||||
}
|
||||
|
||||
public int getMostAvailableStatus() {
|
||||
return this.presences.getMostAvailableStatus();
|
||||
}
|
||||
|
||||
public void setPresences(Presences pres) {
|
||||
this.presences = pres;
|
||||
}
|
||||
|
||||
public void setPhotoUri(String uri) {
|
||||
this.photoUri = uri;
|
||||
}
|
||||
|
||||
public void setServerName(String serverName) {
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
public void setSystemName(String systemName) {
|
||||
this.systemName = systemName;
|
||||
}
|
||||
|
||||
public void setPresenceName(String presenceName) {
|
||||
this.presenceName = presenceName;
|
||||
}
|
||||
|
||||
public String getSystemAccount() {
|
||||
return systemAccount;
|
||||
}
|
||||
|
||||
public Set<String> getOtrFingerprints() {
|
||||
Set<String> set = new HashSet<String>();
|
||||
try {
|
||||
if (this.keys.has("otr_fingerprints")) {
|
||||
JSONArray fingerprints = this.keys
|
||||
.getJSONArray("otr_fingerprints");
|
||||
for (int i = 0; i < fingerprints.length(); ++i) {
|
||||
set.add(fingerprints.getString(i));
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public void addOtrFingerprint(String print) {
|
||||
try {
|
||||
JSONArray fingerprints;
|
||||
if (!this.keys.has("otr_fingerprints")) {
|
||||
fingerprints = new JSONArray();
|
||||
|
||||
} else {
|
||||
fingerprints = this.keys.getJSONArray("otr_fingerprints");
|
||||
}
|
||||
fingerprints.put(print);
|
||||
this.keys.put("otr_fingerprints", fingerprints);
|
||||
} catch (JSONException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void setPgpKeyId(long keyId) {
|
||||
try {
|
||||
this.keys.put("pgp_keyid", keyId);
|
||||
} catch (JSONException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public long getPgpKeyId() {
|
||||
if (this.keys.has("pgp_keyid")) {
|
||||
try {
|
||||
return this.keys.getLong("pgp_keyid");
|
||||
} catch (JSONException e) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void setOption(int option) {
|
||||
this.subscription |= 1 << option;
|
||||
}
|
||||
|
||||
public void resetOption(int option) {
|
||||
this.subscription &= ~(1 << option);
|
||||
}
|
||||
|
||||
public boolean getOption(int option) {
|
||||
return ((this.subscription & (1 << option)) != 0);
|
||||
}
|
||||
|
||||
public boolean showInRoster() {
|
||||
return (this.getOption(Contact.Options.IN_ROSTER) && (!this
|
||||
.getOption(Contact.Options.DIRTY_DELETE)))
|
||||
|| (this.getOption(Contact.Options.DIRTY_PUSH));
|
||||
}
|
||||
|
||||
public void parseSubscriptionFromElement(Element item) {
|
||||
String ask = item.getAttribute("ask");
|
||||
String subscription = item.getAttribute("subscription");
|
||||
|
||||
if (subscription != null) {
|
||||
if (subscription.equals("to")) {
|
||||
this.resetOption(Contact.Options.FROM);
|
||||
this.setOption(Contact.Options.TO);
|
||||
} else if (subscription.equals("from")) {
|
||||
this.resetOption(Contact.Options.TO);
|
||||
this.setOption(Contact.Options.FROM);
|
||||
this.resetOption(Contact.Options.PREEMPTIVE_GRANT);
|
||||
} else if (subscription.equals("both")) {
|
||||
this.setOption(Contact.Options.TO);
|
||||
this.setOption(Contact.Options.FROM);
|
||||
this.resetOption(Contact.Options.PREEMPTIVE_GRANT);
|
||||
} else if (subscription.equals("none")) {
|
||||
this.resetOption(Contact.Options.FROM);
|
||||
this.resetOption(Contact.Options.TO);
|
||||
}
|
||||
}
|
||||
|
||||
// do NOT override asking if pending push request
|
||||
if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
|
||||
if ((ask != null) && (ask.equals("subscribe"))) {
|
||||
this.setOption(Contact.Options.ASKING);
|
||||
} else {
|
||||
this.resetOption(Contact.Options.ASKING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Element asElement() {
|
||||
Element item = new Element("item");
|
||||
item.setAttribute("jid", this.jid);
|
||||
if (this.serverName != null) {
|
||||
item.setAttribute("name", this.serverName);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public class Options {
|
||||
public static final int TO = 0;
|
||||
public static final int FROM = 1;
|
||||
public static final int ASKING = 2;
|
||||
public static final int PREEMPTIVE_GRANT = 3;
|
||||
public static final int IN_ROSTER = 4;
|
||||
public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
|
||||
public static final int DIRTY_PUSH = 6;
|
||||
public static final int DIRTY_DELETE = 7;
|
||||
}
|
||||
|
||||
public class Lastseen {
|
||||
public long time = 0;
|
||||
public String presence = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ListItem another) {
|
||||
return this.getDisplayName().compareToIgnoreCase(
|
||||
another.getDisplayName());
|
||||
}
|
||||
|
||||
public String getServer() {
|
||||
String[] split = getJid().split("@");
|
||||
if (split.length >= 2) {
|
||||
return split[1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean setAvatar(String filename) {
|
||||
if (this.avatar != null && this.avatar.equals(filename)) {
|
||||
return false;
|
||||
} else {
|
||||
this.avatar = filename;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public String getAvatar() {
|
||||
return this.avatar;
|
||||
}
|
||||
|
||||
public boolean deleteOtrFingerprint(String fingerprint) {
|
||||
boolean success = false;
|
||||
try {
|
||||
if (this.keys.has("otr_fingerprints")) {
|
||||
JSONArray newPrints = new JSONArray();
|
||||
JSONArray oldPrints = this.keys
|
||||
.getJSONArray("otr_fingerprints");
|
||||
for (int i = 0; i < oldPrints.length(); ++i) {
|
||||
if (!oldPrints.getString(i).equals(fingerprint)) {
|
||||
newPrints.put(oldPrints.getString(i));
|
||||
} else {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
this.keys.put("otr_fingerprints", newPrints);
|
||||
}
|
||||
return success;
|
||||
} catch (JSONException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean trusted() {
|
||||
return getOption(Options.FROM) && getOption(Options.TO);
|
||||
}
|
||||
}
|
@ -0,0 +1,500 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
import java.security.interfaces.DSAPublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
|
||||
import net.java.otr4j.OtrException;
|
||||
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
|
||||
import net.java.otr4j.crypto.OtrCryptoException;
|
||||
import net.java.otr4j.session.SessionID;
|
||||
import net.java.otr4j.session.SessionImpl;
|
||||
import net.java.otr4j.session.SessionStatus;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.os.SystemClock;
|
||||
|
||||
public class Conversation extends AbstractEntity {
|
||||
public static final String TABLENAME = "conversations";
|
||||
|
||||
public static final int STATUS_AVAILABLE = 0;
|
||||
public static final int STATUS_ARCHIVED = 1;
|
||||
public static final int STATUS_DELETED = 2;
|
||||
|
||||
public static final int MODE_MULTI = 1;
|
||||
public static final int MODE_SINGLE = 0;
|
||||
|
||||
public static final String NAME = "name";
|
||||
public static final String ACCOUNT = "accountUuid";
|
||||
public static final String CONTACT = "contactUuid";
|
||||
public static final String CONTACTJID = "contactJid";
|
||||
public static final String STATUS = "status";
|
||||
public static final String CREATED = "created";
|
||||
public static final String MODE = "mode";
|
||||
public static final String ATTRIBUTES = "attributes";
|
||||
|
||||
public static final String ATTRIBUTE_NEXT_ENCRYPTION = "next_encryption";
|
||||
public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password";
|
||||
public static final String ATTRIBUTE_MUTED_TILL = "muted_till";
|
||||
|
||||
private String name;
|
||||
private String contactUuid;
|
||||
private String accountUuid;
|
||||
private String contactJid;
|
||||
private int status;
|
||||
private long created;
|
||||
private int mode;
|
||||
|
||||
private JSONObject attributes = new JSONObject();
|
||||
|
||||
private String nextPresence;
|
||||
|
||||
protected ArrayList<Message> messages = new ArrayList<Message>();
|
||||
protected Account account = null;
|
||||
|
||||
private transient SessionImpl otrSession;
|
||||
|
||||
private transient String otrFingerprint = null;
|
||||
|
||||
private String nextMessage;
|
||||
|
||||
private transient MucOptions mucOptions = null;
|
||||
|
||||
// private transient String latestMarkableMessageId;
|
||||
|
||||
private byte[] symmetricKey;
|
||||
|
||||
private Bookmark bookmark;
|
||||
|
||||
public Conversation(String name, Account account, String contactJid,
|
||||
int mode) {
|
||||
this(java.util.UUID.randomUUID().toString(), name, null, account
|
||||
.getUuid(), contactJid, System.currentTimeMillis(),
|
||||
STATUS_AVAILABLE, mode, "");
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public Conversation(String uuid, String name, String contactUuid,
|
||||
String accountUuid, String contactJid, long created, int status,
|
||||
int mode, String attributes) {
|
||||
this.uuid = uuid;
|
||||
this.name = name;
|
||||
this.contactUuid = contactUuid;
|
||||
this.accountUuid = accountUuid;
|
||||
this.contactJid = contactJid;
|
||||
this.created = created;
|
||||
this.status = status;
|
||||
this.mode = mode;
|
||||
try {
|
||||
if (attributes == null) {
|
||||
attributes = new String();
|
||||
}
|
||||
this.attributes = new JSONObject(attributes);
|
||||
} catch (JSONException e) {
|
||||
this.attributes = new JSONObject();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Message> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public boolean isRead() {
|
||||
if ((this.messages == null) || (this.messages.size() == 0))
|
||||
return true;
|
||||
return this.messages.get(this.messages.size() - 1).isRead();
|
||||
}
|
||||
|
||||
public void markRead() {
|
||||
if (this.messages == null) {
|
||||
return;
|
||||
}
|
||||
for (int i = this.messages.size() - 1; i >= 0; --i) {
|
||||
if (messages.get(i).isRead()) {
|
||||
break;
|
||||
}
|
||||
this.messages.get(i).markRead();
|
||||
}
|
||||
}
|
||||
|
||||
public String getLatestMarkableMessageId() {
|
||||
if (this.messages == null) {
|
||||
return null;
|
||||
}
|
||||
for (int i = this.messages.size() - 1; i >= 0; --i) {
|
||||
if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED
|
||||
&& this.messages.get(i).markable) {
|
||||
if (this.messages.get(i).isRead()) {
|
||||
return null;
|
||||
} else {
|
||||
return this.messages.get(i).getRemoteMsgId();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Message getLatestMessage() {
|
||||
if ((this.messages == null) || (this.messages.size() == 0)) {
|
||||
Message message = new Message(this, "", Message.ENCRYPTION_NONE);
|
||||
message.setTime(getCreated());
|
||||
return message;
|
||||
} else {
|
||||
Message message = this.messages.get(this.messages.size() - 1);
|
||||
message.setConversation(this);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
public void setMessages(ArrayList<Message> msgs) {
|
||||
this.messages = msgs;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
if (getMode() == MODE_MULTI && getMucOptions().getSubject() != null) {
|
||||
return getMucOptions().getSubject();
|
||||
} else if (getMode() == MODE_MULTI && bookmark != null
|
||||
&& bookmark.getName() != null) {
|
||||
return bookmark.getName();
|
||||
} else {
|
||||
return this.getContact().getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
public String getProfilePhotoString() {
|
||||
return this.getContact().getProfilePhoto();
|
||||
}
|
||||
|
||||
public String getAccountUuid() {
|
||||
return this.accountUuid;
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return this.account;
|
||||
}
|
||||
|
||||
public Contact getContact() {
|
||||
return this.account.getRoster().getContact(this.contactJid);
|
||||
}
|
||||
|
||||
public void setAccount(Account account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public String getContactJid() {
|
||||
return this.contactJid;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public long getCreated() {
|
||||
return this.created;
|
||||
}
|
||||
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(UUID, uuid);
|
||||
values.put(NAME, name);
|
||||
values.put(CONTACT, contactUuid);
|
||||
values.put(ACCOUNT, accountUuid);
|
||||
values.put(CONTACTJID, contactJid);
|
||||
values.put(CREATED, created);
|
||||
values.put(STATUS, status);
|
||||
values.put(MODE, mode);
|
||||
values.put(ATTRIBUTES, attributes.toString());
|
||||
return values;
|
||||
}
|
||||
|
||||
public static Conversation fromCursor(Cursor cursor) {
|
||||
return new Conversation(cursor.getString(cursor.getColumnIndex(UUID)),
|
||||
cursor.getString(cursor.getColumnIndex(NAME)),
|
||||
cursor.getString(cursor.getColumnIndex(CONTACT)),
|
||||
cursor.getString(cursor.getColumnIndex(ACCOUNT)),
|
||||
cursor.getString(cursor.getColumnIndex(CONTACTJID)),
|
||||
cursor.getLong(cursor.getColumnIndex(CREATED)),
|
||||
cursor.getInt(cursor.getColumnIndex(STATUS)),
|
||||
cursor.getInt(cursor.getColumnIndex(MODE)),
|
||||
cursor.getString(cursor.getColumnIndex(ATTRIBUTES)));
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
public void setMode(int mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public SessionImpl startOtrSession(XmppConnectionService service,
|
||||
String presence, boolean sendStart) {
|
||||
if (this.otrSession != null) {
|
||||
return this.otrSession;
|
||||
} else {
|
||||
SessionID sessionId = new SessionID(this.getContactJid().split("/",
|
||||
2)[0], presence, "xmpp");
|
||||
this.otrSession = new SessionImpl(sessionId, getAccount()
|
||||
.getOtrEngine(service));
|
||||
try {
|
||||
if (sendStart) {
|
||||
this.otrSession.startSession();
|
||||
return this.otrSession;
|
||||
}
|
||||
return this.otrSession;
|
||||
} catch (OtrException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SessionImpl getOtrSession() {
|
||||
return this.otrSession;
|
||||
}
|
||||
|
||||
public void resetOtrSession() {
|
||||
this.otrFingerprint = null;
|
||||
this.otrSession = null;
|
||||
}
|
||||
|
||||
public void startOtrIfNeeded() {
|
||||
if (this.otrSession != null
|
||||
&& this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) {
|
||||
try {
|
||||
this.otrSession.startSession();
|
||||
} catch (OtrException e) {
|
||||
this.resetOtrSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean endOtrIfNeeded() {
|
||||
if (this.otrSession != null) {
|
||||
if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
|
||||
try {
|
||||
this.otrSession.endSession();
|
||||
this.resetOtrSession();
|
||||
return true;
|
||||
} catch (OtrException e) {
|
||||
this.resetOtrSession();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
this.resetOtrSession();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasValidOtrSession() {
|
||||
return this.otrSession != null;
|
||||
}
|
||||
|
||||
public String getOtrFingerprint() {
|
||||
if (this.otrFingerprint == null) {
|
||||
try {
|
||||
if (getOtrSession() == null) {
|
||||
return "";
|
||||
}
|
||||
DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession()
|
||||
.getRemotePublicKey();
|
||||
StringBuilder builder = new StringBuilder(
|
||||
new OtrCryptoEngineImpl().getFingerprint(remotePubKey));
|
||||
builder.insert(8, " ");
|
||||
builder.insert(17, " ");
|
||||
builder.insert(26, " ");
|
||||
builder.insert(35, " ");
|
||||
this.otrFingerprint = builder.toString();
|
||||
} catch (OtrCryptoException e) {
|
||||
|
||||
}
|
||||
}
|
||||
return this.otrFingerprint;
|
||||
}
|
||||
|
||||
public synchronized MucOptions getMucOptions() {
|
||||
if (this.mucOptions == null) {
|
||||
this.mucOptions = new MucOptions(this);
|
||||
}
|
||||
return this.mucOptions;
|
||||
}
|
||||
|
||||
public void resetMucOptions() {
|
||||
this.mucOptions = null;
|
||||
}
|
||||
|
||||
public void setContactJid(String jid) {
|
||||
this.contactJid = jid;
|
||||
}
|
||||
|
||||
public void setNextPresence(String presence) {
|
||||
this.nextPresence = presence;
|
||||
}
|
||||
|
||||
public String getNextPresence() {
|
||||
return this.nextPresence;
|
||||
}
|
||||
|
||||
public int getLatestEncryption() {
|
||||
int latestEncryption = this.getLatestMessage().getEncryption();
|
||||
if ((latestEncryption == Message.ENCRYPTION_DECRYPTED)
|
||||
|| (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) {
|
||||
return Message.ENCRYPTION_PGP;
|
||||
} else {
|
||||
return latestEncryption;
|
||||
}
|
||||
}
|
||||
|
||||
public int getNextEncryption(boolean force) {
|
||||
int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
|
||||
if (next == -1) {
|
||||
int latest = this.getLatestEncryption();
|
||||
if (latest == Message.ENCRYPTION_NONE) {
|
||||
if (force && getMode() == MODE_SINGLE) {
|
||||
return Message.ENCRYPTION_OTR;
|
||||
} else if (getContact().getPresences().size() == 1) {
|
||||
if (getContact().getOtrFingerprints().size() >= 1) {
|
||||
return Message.ENCRYPTION_OTR;
|
||||
} else {
|
||||
return latest;
|
||||
}
|
||||
} else {
|
||||
return latest;
|
||||
}
|
||||
} else {
|
||||
return latest;
|
||||
}
|
||||
}
|
||||
if (next == Message.ENCRYPTION_NONE && force
|
||||
&& getMode() == MODE_SINGLE) {
|
||||
return Message.ENCRYPTION_OTR;
|
||||
} else {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
|
||||
public void setNextEncryption(int encryption) {
|
||||
this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, String.valueOf(encryption));
|
||||
}
|
||||
|
||||
public String getNextMessage() {
|
||||
if (this.nextMessage == null) {
|
||||
return "";
|
||||
} else {
|
||||
return this.nextMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public void setNextMessage(String message) {
|
||||
this.nextMessage = message;
|
||||
}
|
||||
|
||||
public void setSymmetricKey(byte[] key) {
|
||||
this.symmetricKey = key;
|
||||
}
|
||||
|
||||
public byte[] getSymmetricKey() {
|
||||
return this.symmetricKey;
|
||||
}
|
||||
|
||||
public void setBookmark(Bookmark bookmark) {
|
||||
this.bookmark = bookmark;
|
||||
this.bookmark.setConversation(this);
|
||||
}
|
||||
|
||||
public void deregisterWithBookmark() {
|
||||
if (this.bookmark != null) {
|
||||
this.bookmark.setConversation(null);
|
||||
}
|
||||
}
|
||||
|
||||
public Bookmark getBookmark() {
|
||||
return this.bookmark;
|
||||
}
|
||||
|
||||
public boolean hasDuplicateMessage(Message message) {
|
||||
for (int i = this.getMessages().size() - 1; i >= 0; --i) {
|
||||
if (this.messages.get(i).equals(message)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setMutedTill(long value) {
|
||||
this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value));
|
||||
}
|
||||
|
||||
public boolean isMuted() {
|
||||
return SystemClock.elapsedRealtime() < this.getLongAttribute(
|
||||
ATTRIBUTE_MUTED_TILL, 0);
|
||||
}
|
||||
|
||||
public boolean setAttribute(String key, String value) {
|
||||
try {
|
||||
this.attributes.put(key, value);
|
||||
return true;
|
||||
} catch (JSONException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getAttribute(String key) {
|
||||
try {
|
||||
return this.attributes.getString(key);
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int getIntAttribute(String key, int defaultValue) {
|
||||
String value = this.getAttribute(key);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long getLongAttribute(String key, long defaultValue) {
|
||||
String value = this.getAttribute(key);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void add(Message message) {
|
||||
message.setConversation(this);
|
||||
synchronized (this.messages) {
|
||||
this.messages.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void addAll(int index, List<Message> messages) {
|
||||
synchronized (this.messages) {
|
||||
this.messages.addAll(index, messages);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
public interface Downloadable {
|
||||
|
||||
public final String[] VALID_EXTENSIONS = { "webp", "jpeg", "jpg", "png" };
|
||||
public final String[] VALID_CRYPTO_EXTENSIONS = { "pgp", "gpg", "otr" };
|
||||
|
||||
public static final int STATUS_UNKNOWN = 0x200;
|
||||
public static final int STATUS_CHECKING = 0x201;
|
||||
public static final int STATUS_FAILED = 0x202;
|
||||
public static final int STATUS_OFFER = 0x203;
|
||||
public static final int STATUS_DOWNLOADING = 0x204;
|
||||
public static final int STATUS_DELETED = 0x205;
|
||||
public static final int STATUS_OFFER_CHECK_FILESIZE = 0x206;
|
||||
|
||||
public boolean start();
|
||||
|
||||
public int getStatus();
|
||||
|
||||
public long getFileSize();
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import android.util.Log;
|
||||
|
||||
public class DownloadableFile extends File {
|
||||
|
||||
private static final long serialVersionUID = 2247012619505115863L;
|
||||
|
||||
private long expectedSize = 0;
|
||||
private String sha1sum;
|
||||
private Key aeskey;
|
||||
|
||||
private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
|
||||
|
||||
public DownloadableFile(String path) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return super.length();
|
||||
}
|
||||
|
||||
public long getExpectedSize() {
|
||||
if (this.aeskey != null) {
|
||||
if (this.expectedSize == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return (this.expectedSize / 16 + 1) * 16;
|
||||
}
|
||||
} else {
|
||||
return this.expectedSize;
|
||||
}
|
||||
}
|
||||
|
||||
public void setExpectedSize(long size) {
|
||||
this.expectedSize = size;
|
||||
}
|
||||
|
||||
public String getSha1Sum() {
|
||||
return this.sha1sum;
|
||||
}
|
||||
|
||||
public void setSha1Sum(String sum) {
|
||||
this.sha1sum = sum;
|
||||
}
|
||||
|
||||
public void setKey(byte[] key) {
|
||||
if (key.length == 48) {
|
||||
byte[] secretKey = new byte[32];
|
||||
byte[] iv = new byte[16];
|
||||
System.arraycopy(key, 0, iv, 0, 16);
|
||||
System.arraycopy(key, 16, secretKey, 0, 32);
|
||||
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
||||
this.iv = iv;
|
||||
} else if (key.length >= 32) {
|
||||
byte[] secretKey = new byte[32];
|
||||
System.arraycopy(key, 0, secretKey, 0, 32);
|
||||
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
||||
} else if (key.length >= 16) {
|
||||
byte[] secretKey = new byte[16];
|
||||
System.arraycopy(key, 0, secretKey, 0, 16);
|
||||
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
||||
}
|
||||
}
|
||||
|
||||
public Key getKey() {
|
||||
return this.aeskey;
|
||||
}
|
||||
|
||||
public InputStream createInputStream() {
|
||||
if (this.getKey() == null) {
|
||||
try {
|
||||
return new FileInputStream(this);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
IvParameterSpec ips = new IvParameterSpec(iv);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, this.getKey(), ips);
|
||||
Log.d(Config.LOGTAG, "opening encrypted input stream");
|
||||
return new CipherInputStream(new FileInputStream(this), cipher);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
|
||||
return null;
|
||||
} catch (NoSuchPaddingException e) {
|
||||
Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
|
||||
return null;
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
|
||||
return null;
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
|
||||
return null;
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public OutputStream createOutputStream() {
|
||||
if (this.getKey() == null) {
|
||||
try {
|
||||
return new FileOutputStream(this);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
IvParameterSpec ips = new IvParameterSpec(this.iv);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, this.getKey(), ips);
|
||||
Log.d(Config.LOGTAG, "opening encrypted output stream");
|
||||
return new CipherOutputStream(new FileOutputStream(this),
|
||||
cipher);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
|
||||
return null;
|
||||
} catch (NoSuchPaddingException e) {
|
||||
Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
|
||||
return null;
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
|
||||
return null;
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
|
||||
return null;
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
public interface ListItem extends Comparable<ListItem> {
|
||||
public String getDisplayName();
|
||||
|
||||
public String getJid();
|
||||
}
|
@ -0,0 +1,478 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
public class Message extends AbstractEntity {
|
||||
|
||||
public static final String TABLENAME = "messages";
|
||||
|
||||
public static final int STATUS_RECEIVED = 0;
|
||||
public static final int STATUS_UNSEND = 1;
|
||||
public static final int STATUS_SEND = 2;
|
||||
public static final int STATUS_SEND_FAILED = 3;
|
||||
public static final int STATUS_SEND_REJECTED = 4;
|
||||
public static final int STATUS_WAITING = 5;
|
||||
public static final int STATUS_OFFERED = 6;
|
||||
public static final int STATUS_SEND_RECEIVED = 7;
|
||||
public static final int STATUS_SEND_DISPLAYED = 8;
|
||||
|
||||
public static final int ENCRYPTION_NONE = 0;
|
||||
public static final int ENCRYPTION_PGP = 1;
|
||||
public static final int ENCRYPTION_OTR = 2;
|
||||
public static final int ENCRYPTION_DECRYPTED = 3;
|
||||
public static final int ENCRYPTION_DECRYPTION_FAILED = 4;
|
||||
|
||||
public static final int TYPE_TEXT = 0;
|
||||
public static final int TYPE_IMAGE = 1;
|
||||
public static final int TYPE_AUDIO = 2;
|
||||
public static final int TYPE_STATUS = 3;
|
||||
public static final int TYPE_PRIVATE = 4;
|
||||
|
||||
public static String CONVERSATION = "conversationUuid";
|
||||
public static String COUNTERPART = "counterpart";
|
||||
public static String TRUE_COUNTERPART = "trueCounterpart";
|
||||
public static String BODY = "body";
|
||||
public static String TIME_SENT = "timeSent";
|
||||
public static String ENCRYPTION = "encryption";
|
||||
public static String STATUS = "status";
|
||||
public static String TYPE = "type";
|
||||
public static String REMOTE_MSG_ID = "remoteMsgId";
|
||||
|
||||
protected String conversationUuid;
|
||||
protected String counterpart;
|
||||
protected String trueCounterpart;
|
||||
protected String body;
|
||||
protected String encryptedBody;
|
||||
protected long timeSent;
|
||||
protected int encryption;
|
||||
protected int status;
|
||||
protected int type;
|
||||
protected boolean read = true;
|
||||
protected String remoteMsgId = null;
|
||||
|
||||
protected Conversation conversation = null;
|
||||
protected Downloadable downloadable = null;
|
||||
public boolean markable = false;
|
||||
|
||||
private Message mNextMessage = null;
|
||||
private Message mPreviousMessage = null;
|
||||
|
||||
private Message() {
|
||||
|
||||
}
|
||||
|
||||
public Message(Conversation conversation, String body, int encryption) {
|
||||
this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
|
||||
conversation.getContactJid(), null, body, System
|
||||
.currentTimeMillis(), encryption,
|
||||
Message.STATUS_UNSEND, TYPE_TEXT, null);
|
||||
this.conversation = conversation;
|
||||
}
|
||||
|
||||
public Message(Conversation conversation, String counterpart, String body,
|
||||
int encryption, int status) {
|
||||
this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
|
||||
counterpart, null, body, System.currentTimeMillis(),
|
||||
encryption, status, TYPE_TEXT, null);
|
||||
this.conversation = conversation;
|
||||
}
|
||||
|
||||
public Message(String uuid, String conversationUUid, String counterpart,
|
||||
String trueCounterpart, String body, long timeSent, int encryption,
|
||||
int status, int type, String remoteMsgId) {
|
||||
this.uuid = uuid;
|
||||
this.conversationUuid = conversationUUid;
|
||||
this.counterpart = counterpart;
|
||||
this.trueCounterpart = trueCounterpart;
|
||||
this.body = body;
|
||||
this.timeSent = timeSent;
|
||||
this.encryption = encryption;
|
||||
this.status = status;
|
||||
this.type = type;
|
||||
this.remoteMsgId = remoteMsgId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(UUID, uuid);
|
||||
values.put(CONVERSATION, conversationUuid);
|
||||
values.put(COUNTERPART, counterpart);
|
||||
values.put(TRUE_COUNTERPART, trueCounterpart);
|
||||
values.put(BODY, body);
|
||||
values.put(TIME_SENT, timeSent);
|
||||
values.put(ENCRYPTION, encryption);
|
||||
values.put(STATUS, status);
|
||||
values.put(TYPE, type);
|
||||
values.put(REMOTE_MSG_ID, remoteMsgId);
|
||||
return values;
|
||||
}
|
||||
|
||||
public String getConversationUuid() {
|
||||
return conversationUuid;
|
||||
}
|
||||
|
||||
public Conversation getConversation() {
|
||||
return this.conversation;
|
||||
}
|
||||
|
||||
public String getCounterpart() {
|
||||
return counterpart;
|
||||
}
|
||||
|
||||
public Contact getContact() {
|
||||
if (this.conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||
return this.conversation.getContact();
|
||||
} else {
|
||||
if (this.trueCounterpart == null) {
|
||||
return null;
|
||||
} else {
|
||||
return this.conversation.getAccount().getRoster()
|
||||
.getContactFromRoster(this.trueCounterpart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public String getReadableBody(Context context) {
|
||||
if (encryption == ENCRYPTION_PGP) {
|
||||
return context.getText(R.string.encrypted_message_received)
|
||||
.toString();
|
||||
} else if (encryption == ENCRYPTION_DECRYPTION_FAILED) {
|
||||
return context.getText(R.string.decryption_failed).toString();
|
||||
} else if (type == TYPE_IMAGE) {
|
||||
return context.getText(R.string.image_file).toString();
|
||||
} else {
|
||||
return body.trim();
|
||||
}
|
||||
}
|
||||
|
||||
public long getTimeSent() {
|
||||
return timeSent;
|
||||
}
|
||||
|
||||
public int getEncryption() {
|
||||
return encryption;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getRemoteMsgId() {
|
||||
return this.remoteMsgId;
|
||||
}
|
||||
|
||||
public void setRemoteMsgId(String id) {
|
||||
this.remoteMsgId = id;
|
||||
}
|
||||
|
||||
public static Message fromCursor(Cursor cursor) {
|
||||
return new Message(cursor.getString(cursor.getColumnIndex(UUID)),
|
||||
cursor.getString(cursor.getColumnIndex(CONVERSATION)),
|
||||
cursor.getString(cursor.getColumnIndex(COUNTERPART)),
|
||||
cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART)),
|
||||
cursor.getString(cursor.getColumnIndex(BODY)),
|
||||
cursor.getLong(cursor.getColumnIndex(TIME_SENT)),
|
||||
cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
|
||||
cursor.getInt(cursor.getColumnIndex(STATUS)),
|
||||
cursor.getInt(cursor.getColumnIndex(TYPE)),
|
||||
cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)));
|
||||
}
|
||||
|
||||
public void setConversation(Conversation conv) {
|
||||
this.conversation = conv;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public boolean isRead() {
|
||||
return this.read;
|
||||
}
|
||||
|
||||
public void markRead() {
|
||||
this.read = true;
|
||||
}
|
||||
|
||||
public void markUnread() {
|
||||
this.read = false;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.timeSent = time;
|
||||
}
|
||||
|
||||
public void setEncryption(int encryption) {
|
||||
this.encryption = encryption;
|
||||
}
|
||||
|
||||
public void setBody(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public String getEncryptedBody() {
|
||||
return this.encryptedBody;
|
||||
}
|
||||
|
||||
public void setEncryptedBody(String body) {
|
||||
this.encryptedBody = body;
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public void setPresence(String presence) {
|
||||
if (presence == null) {
|
||||
this.counterpart = this.counterpart.split("/", 2)[0];
|
||||
} else {
|
||||
this.counterpart = this.counterpart.split("/", 2)[0] + "/"
|
||||
+ presence;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTrueCounterpart(String trueCounterpart) {
|
||||
this.trueCounterpart = trueCounterpart;
|
||||
}
|
||||
|
||||
public String getPresence() {
|
||||
String[] counterparts = this.counterpart.split("/", 2);
|
||||
if (counterparts.length == 2) {
|
||||
return counterparts[1];
|
||||
} else {
|
||||
if (this.counterpart.contains("/")) {
|
||||
return "";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setDownloadable(Downloadable downloadable) {
|
||||
this.downloadable = downloadable;
|
||||
}
|
||||
|
||||
public Downloadable getDownloadable() {
|
||||
return this.downloadable;
|
||||
}
|
||||
|
||||
public static Message createStatusMessage(Conversation conversation) {
|
||||
Message message = new Message();
|
||||
message.setType(Message.TYPE_STATUS);
|
||||
message.setConversation(conversation);
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setCounterpart(String counterpart) {
|
||||
this.counterpart = counterpart;
|
||||
}
|
||||
|
||||
public boolean equals(Message message) {
|
||||
if ((this.remoteMsgId != null) && (this.body != null)
|
||||
&& (this.counterpart != null)) {
|
||||
return this.remoteMsgId.equals(message.getRemoteMsgId())
|
||||
&& this.body.equals(message.getBody())
|
||||
&& this.counterpart.equals(message.getCounterpart());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Message next() {
|
||||
if (this.mNextMessage == null) {
|
||||
synchronized (this.conversation.messages) {
|
||||
int index = this.conversation.messages.indexOf(this);
|
||||
if (index < 0
|
||||
|| index >= this.conversation.getMessages().size() - 1) {
|
||||
this.mNextMessage = null;
|
||||
} else {
|
||||
this.mNextMessage = this.conversation.messages
|
||||
.get(index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.mNextMessage;
|
||||
}
|
||||
|
||||
public Message prev() {
|
||||
if (this.mPreviousMessage == null) {
|
||||
synchronized (this.conversation.messages) {
|
||||
int index = this.conversation.messages.indexOf(this);
|
||||
if (index <= 0 || index > this.conversation.messages.size()) {
|
||||
this.mPreviousMessage = null;
|
||||
} else {
|
||||
this.mPreviousMessage = this.conversation.messages
|
||||
.get(index - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.mPreviousMessage;
|
||||
}
|
||||
|
||||
public boolean mergable(Message message) {
|
||||
if (message == null) {
|
||||
return false;
|
||||
}
|
||||
return (message.getType() == Message.TYPE_TEXT
|
||||
&& this.getDownloadable() == null
|
||||
&& message.getDownloadable() == null
|
||||
&& message.getEncryption() != Message.ENCRYPTION_PGP
|
||||
&& this.getType() == message.getType()
|
||||
&& this.getEncryption() == message.getEncryption()
|
||||
&& this.getCounterpart().equals(message.getCounterpart())
|
||||
&& (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && ((this
|
||||
.getStatus() == message.getStatus() || ((this.getStatus() == Message.STATUS_SEND || this
|
||||
.getStatus() == Message.STATUS_SEND_RECEIVED) && (message
|
||||
.getStatus() == Message.STATUS_UNSEND
|
||||
|| message.getStatus() == Message.STATUS_SEND || message
|
||||
.getStatus() == Message.STATUS_SEND_DISPLAYED)))));
|
||||
}
|
||||
|
||||
public String getMergedBody() {
|
||||
Message next = this.next();
|
||||
if (this.mergable(next)) {
|
||||
return body.trim() + '\n' + next.getMergedBody();
|
||||
}
|
||||
return body.trim();
|
||||
}
|
||||
|
||||
public int getMergedStatus() {
|
||||
Message next = this.next();
|
||||
if (this.mergable(next)) {
|
||||
return next.getMergedStatus();
|
||||
} else {
|
||||
return getStatus();
|
||||
}
|
||||
}
|
||||
|
||||
public long getMergedTimeSent() {
|
||||
Message next = this.next();
|
||||
if (this.mergable(next)) {
|
||||
return next.getMergedTimeSent();
|
||||
} else {
|
||||
return getTimeSent();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean wasMergedIntoPrevious() {
|
||||
Message prev = this.prev();
|
||||
if (prev == null) {
|
||||
return false;
|
||||
} else {
|
||||
return prev.mergable(this);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean bodyContainsDownloadable() {
|
||||
Contact contact = this.getContact();
|
||||
if (status <= STATUS_RECEIVED
|
||||
&& (contact == null || !contact.trusted())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
URL url = new URL(this.getBody());
|
||||
if (!url.getProtocol().equalsIgnoreCase("http")
|
||||
&& !url.getProtocol().equalsIgnoreCase("https")) {
|
||||
return false;
|
||||
}
|
||||
if (url.getPath() == null) {
|
||||
return false;
|
||||
}
|
||||
String[] pathParts = url.getPath().split("/");
|
||||
String filename = pathParts[pathParts.length - 1];
|
||||
String[] extensionParts = filename.split("\\.");
|
||||
if (extensionParts.length == 2
|
||||
&& Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
|
||||
extensionParts[extensionParts.length - 1])) {
|
||||
return true;
|
||||
} else if (extensionParts.length == 3
|
||||
&& Arrays
|
||||
.asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
|
||||
.contains(extensionParts[extensionParts.length - 1])
|
||||
&& Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
|
||||
extensionParts[extensionParts.length - 2])) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public ImageParams getImageParams() {
|
||||
ImageParams params = new ImageParams();
|
||||
if (this.downloadable != null) {
|
||||
params.size = this.downloadable.getFileSize();
|
||||
}
|
||||
if (body == null) {
|
||||
return params;
|
||||
}
|
||||
String parts[] = body.split(",");
|
||||
if (parts.length == 1) {
|
||||
try {
|
||||
params.size = Long.parseLong(parts[0]);
|
||||
} catch (NumberFormatException e) {
|
||||
params.origin = parts[0];
|
||||
}
|
||||
} else if (parts.length == 3) {
|
||||
try {
|
||||
params.size = Long.parseLong(parts[0]);
|
||||
} catch (NumberFormatException e) {
|
||||
params.size = 0;
|
||||
}
|
||||
try {
|
||||
params.width = Integer.parseInt(parts[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
params.width = 0;
|
||||
}
|
||||
try {
|
||||
params.height = Integer.parseInt(parts[2]);
|
||||
} catch (NumberFormatException e) {
|
||||
params.height = 0;
|
||||
}
|
||||
} else if (parts.length == 4) {
|
||||
params.origin = parts[0];
|
||||
try {
|
||||
params.size = Long.parseLong(parts[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
params.size = 0;
|
||||
}
|
||||
try {
|
||||
params.width = Integer.parseInt(parts[2]);
|
||||
} catch (NumberFormatException e) {
|
||||
params.width = 0;
|
||||
}
|
||||
try {
|
||||
params.height = Integer.parseInt(parts[3]);
|
||||
} catch (NumberFormatException e) {
|
||||
params.height = 0;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
public class ImageParams {
|
||||
public long size = 0;
|
||||
public int width = 0;
|
||||
public int height = 0;
|
||||
public String origin;
|
||||
}
|
||||
}
|
@ -0,0 +1,369 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import eu.siacs.conversations.crypto.PgpEngine;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public class MucOptions {
|
||||
public static final int ERROR_NO_ERROR = 0;
|
||||
public static final int ERROR_NICK_IN_USE = 1;
|
||||
public static final int ERROR_ROOM_NOT_FOUND = 2;
|
||||
public static final int ERROR_PASSWORD_REQUIRED = 3;
|
||||
public static final int ERROR_BANNED = 4;
|
||||
public static final int ERROR_MEMBERS_ONLY = 5;
|
||||
|
||||
public static final int KICKED_FROM_ROOM = 9;
|
||||
|
||||
public static final String STATUS_CODE_BANNED = "301";
|
||||
public static final String STATUS_CODE_KICKED = "307";
|
||||
|
||||
public interface OnRenameListener {
|
||||
public void onRename(boolean success);
|
||||
}
|
||||
|
||||
public class User {
|
||||
public static final int ROLE_MODERATOR = 3;
|
||||
public static final int ROLE_NONE = 0;
|
||||
public static final int ROLE_PARTICIPANT = 2;
|
||||
public static final int ROLE_VISITOR = 1;
|
||||
public static final int AFFILIATION_ADMIN = 4;
|
||||
public static final int AFFILIATION_OWNER = 3;
|
||||
public static final int AFFILIATION_MEMBER = 2;
|
||||
public static final int AFFILIATION_OUTCAST = 1;
|
||||
public static final int AFFILIATION_NONE = 0;
|
||||
|
||||
private int role;
|
||||
private int affiliation;
|
||||
private String name;
|
||||
private String jid;
|
||||
private long pgpKeyId = 0;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String user) {
|
||||
this.name = user;
|
||||
}
|
||||
|
||||
public void setJid(String jid) {
|
||||
this.jid = jid;
|
||||
}
|
||||
|
||||
public String getJid() {
|
||||
return this.jid;
|
||||
}
|
||||
|
||||
public int getRole() {
|
||||
return this.role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
role = role.toLowerCase();
|
||||
if (role.equals("moderator")) {
|
||||
this.role = ROLE_MODERATOR;
|
||||
} else if (role.equals("participant")) {
|
||||
this.role = ROLE_PARTICIPANT;
|
||||
} else if (role.equals("visitor")) {
|
||||
this.role = ROLE_VISITOR;
|
||||
} else {
|
||||
this.role = ROLE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public int getAffiliation() {
|
||||
return this.affiliation;
|
||||
}
|
||||
|
||||
public void setAffiliation(String affiliation) {
|
||||
if (affiliation.equalsIgnoreCase("admin")) {
|
||||
this.affiliation = AFFILIATION_ADMIN;
|
||||
} else if (affiliation.equalsIgnoreCase("owner")) {
|
||||
this.affiliation = AFFILIATION_OWNER;
|
||||
} else if (affiliation.equalsIgnoreCase("member")) {
|
||||
this.affiliation = AFFILIATION_MEMBER;
|
||||
} else if (affiliation.equalsIgnoreCase("outcast")) {
|
||||
this.affiliation = AFFILIATION_OUTCAST;
|
||||
} else {
|
||||
this.affiliation = AFFILIATION_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public void setPgpKeyId(long id) {
|
||||
this.pgpKeyId = id;
|
||||
}
|
||||
|
||||
public long getPgpKeyId() {
|
||||
return this.pgpKeyId;
|
||||
}
|
||||
|
||||
public Contact getContact() {
|
||||
return account.getRoster().getContactFromRoster(getJid());
|
||||
}
|
||||
}
|
||||
|
||||
private Account account;
|
||||
private List<User> users = new CopyOnWriteArrayList<User>();
|
||||
private Conversation conversation;
|
||||
private boolean isOnline = false;
|
||||
private int error = ERROR_ROOM_NOT_FOUND;
|
||||
private OnRenameListener renameListener = null;
|
||||
private boolean aboutToRename = false;
|
||||
private User self = new User();
|
||||
private String subject = null;
|
||||
private String joinnick;
|
||||
private String password = null;
|
||||
|
||||
public MucOptions(Conversation conversation) {
|
||||
this.account = conversation.getAccount();
|
||||
this.conversation = conversation;
|
||||
}
|
||||
|
||||
public void deleteUser(String name) {
|
||||
for (int i = 0; i < users.size(); ++i) {
|
||||
if (users.get(i).getName().equals(name)) {
|
||||
users.remove(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addUser(User user) {
|
||||
for (int i = 0; i < users.size(); ++i) {
|
||||
if (users.get(i).getName().equals(user.getName())) {
|
||||
users.set(i, user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
users.add(user);
|
||||
}
|
||||
|
||||
public void processPacket(PresencePacket packet, PgpEngine pgp) {
|
||||
String[] fromParts = packet.getFrom().split("/", 2);
|
||||
if (fromParts.length >= 2) {
|
||||
String name = fromParts[1];
|
||||
String type = packet.getAttribute("type");
|
||||
if (type == null) {
|
||||
User user = new User();
|
||||
Element item = packet.findChild("x",
|
||||
"http://jabber.org/protocol/muc#user")
|
||||
.findChild("item");
|
||||
user.setName(name);
|
||||
user.setAffiliation(item.getAttribute("affiliation"));
|
||||
user.setRole(item.getAttribute("role"));
|
||||
user.setJid(item.getAttribute("jid"));
|
||||
user.setName(name);
|
||||
if (name.equals(this.joinnick)) {
|
||||
this.isOnline = true;
|
||||
this.error = ERROR_NO_ERROR;
|
||||
self = user;
|
||||
if (aboutToRename) {
|
||||
if (renameListener != null) {
|
||||
renameListener.onRename(true);
|
||||
}
|
||||
aboutToRename = false;
|
||||
}
|
||||
} else {
|
||||
addUser(user);
|
||||
}
|
||||
if (pgp != null) {
|
||||
Element x = packet.findChild("x", "jabber:x:signed");
|
||||
if (x != null) {
|
||||
Element status = packet.findChild("status");
|
||||
String msg;
|
||||
if (status != null) {
|
||||
msg = status.getContent();
|
||||
} else {
|
||||
msg = "";
|
||||
}
|
||||
user.setPgpKeyId(pgp.fetchKeyId(account, msg,
|
||||
x.getContent()));
|
||||
}
|
||||
}
|
||||
} else if (type.equals("unavailable") && name.equals(this.joinnick)) {
|
||||
Element x = packet.findChild("x",
|
||||
"http://jabber.org/protocol/muc#user");
|
||||
if (x != null) {
|
||||
Element status = x.findChild("status");
|
||||
if (status != null) {
|
||||
String code = status.getAttribute("code");
|
||||
if (STATUS_CODE_KICKED.equals(code)) {
|
||||
this.isOnline = false;
|
||||
this.error = KICKED_FROM_ROOM;
|
||||
} else if (STATUS_CODE_BANNED.equals(code)) {
|
||||
this.isOnline = false;
|
||||
this.error = ERROR_BANNED;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type.equals("unavailable")) {
|
||||
deleteUser(packet.getAttribute("from").split("/", 2)[1]);
|
||||
} else if (type.equals("error")) {
|
||||
Element error = packet.findChild("error");
|
||||
if (error != null && error.hasChild("conflict")) {
|
||||
if (aboutToRename) {
|
||||
if (renameListener != null) {
|
||||
renameListener.onRename(false);
|
||||
}
|
||||
aboutToRename = false;
|
||||
this.setJoinNick(getActualNick());
|
||||
} else {
|
||||
this.error = ERROR_NICK_IN_USE;
|
||||
}
|
||||
} else if (error != null && error.hasChild("not-authorized")) {
|
||||
this.error = ERROR_PASSWORD_REQUIRED;
|
||||
} else if (error != null && error.hasChild("forbidden")) {
|
||||
this.error = ERROR_BANNED;
|
||||
} else if (error != null
|
||||
&& error.hasChild("registration-required")) {
|
||||
this.error = ERROR_MEMBERS_ONLY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<User> getUsers() {
|
||||
return this.users;
|
||||
}
|
||||
|
||||
public String getProposedNick() {
|
||||
String[] mucParts = conversation.getContactJid().split("/", 2);
|
||||
if (conversation.getBookmark() != null
|
||||
&& conversation.getBookmark().getNick() != null) {
|
||||
return conversation.getBookmark().getNick();
|
||||
} else {
|
||||
if (mucParts.length == 2) {
|
||||
return mucParts[1];
|
||||
} else {
|
||||
return account.getUsername();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getActualNick() {
|
||||
if (this.self.getName() != null) {
|
||||
return this.self.getName();
|
||||
} else {
|
||||
return this.getProposedNick();
|
||||
}
|
||||
}
|
||||
|
||||
public void setJoinNick(String nick) {
|
||||
this.joinnick = nick;
|
||||
}
|
||||
|
||||
public boolean online() {
|
||||
return this.isOnline;
|
||||
}
|
||||
|
||||
public int getError() {
|
||||
return this.error;
|
||||
}
|
||||
|
||||
public void setOnRenameListener(OnRenameListener listener) {
|
||||
this.renameListener = listener;
|
||||
}
|
||||
|
||||
public OnRenameListener getOnRenameListener() {
|
||||
return this.renameListener;
|
||||
}
|
||||
|
||||
public void setOffline() {
|
||||
this.users.clear();
|
||||
this.error = 0;
|
||||
this.isOnline = false;
|
||||
}
|
||||
|
||||
public User getSelf() {
|
||||
return self;
|
||||
}
|
||||
|
||||
public void setSubject(String content) {
|
||||
this.subject = content;
|
||||
}
|
||||
|
||||
public String getSubject() {
|
||||
return this.subject;
|
||||
}
|
||||
|
||||
public void flagAboutToRename() {
|
||||
this.aboutToRename = true;
|
||||
}
|
||||
|
||||
public long[] getPgpKeyIds() {
|
||||
List<Long> ids = new ArrayList<Long>();
|
||||
for (User user : getUsers()) {
|
||||
if (user.getPgpKeyId() != 0) {
|
||||
ids.add(user.getPgpKeyId());
|
||||
}
|
||||
}
|
||||
long[] primitivLongArray = new long[ids.size()];
|
||||
for (int i = 0; i < ids.size(); ++i) {
|
||||
primitivLongArray[i] = ids.get(i);
|
||||
}
|
||||
return primitivLongArray;
|
||||
}
|
||||
|
||||
public boolean pgpKeysInUse() {
|
||||
for (User user : getUsers()) {
|
||||
if (user.getPgpKeyId() != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean everybodyHasKeys() {
|
||||
for (User user : getUsers()) {
|
||||
if (user.getPgpKeyId() == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getJoinJid() {
|
||||
return this.conversation.getContactJid().split("/", 2)[0] + "/"
|
||||
+ this.joinnick;
|
||||
}
|
||||
|
||||
public String getTrueCounterpart(String counterpart) {
|
||||
for (User user : this.getUsers()) {
|
||||
if (user.getName().equals(counterpart)) {
|
||||
return user.getJid();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
this.password = conversation
|
||||
.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
|
||||
if (this.password == null && conversation.getBookmark() != null
|
||||
&& conversation.getBookmark().getPassword() != null) {
|
||||
return conversation.getBookmark().getPassword();
|
||||
} else {
|
||||
return this.password;
|
||||
}
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
if (conversation.getBookmark() != null) {
|
||||
conversation.getBookmark().setPassword(password);
|
||||
} else {
|
||||
this.password = password;
|
||||
}
|
||||
conversation
|
||||
.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
|
||||
}
|
||||
|
||||
public Conversation getConversation() {
|
||||
return this.conversation;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
|
||||
public class Presences {
|
||||
|
||||
public static final int CHAT = -1;
|
||||
public static final int ONLINE = 0;
|
||||
public static final int AWAY = 1;
|
||||
public static final int XA = 2;
|
||||
public static final int DND = 3;
|
||||
public static final int OFFLINE = 4;
|
||||
|
||||
private Hashtable<String, Integer> presences = new Hashtable<String, Integer>();
|
||||
|
||||
public Hashtable<String, Integer> getPresences() {
|
||||
return this.presences;
|
||||
}
|
||||
|
||||
public void updatePresence(String resource, int status) {
|
||||
this.presences.put(resource, status);
|
||||
}
|
||||
|
||||
public void removePresence(String resource) {
|
||||
this.presences.remove(resource);
|
||||
}
|
||||
|
||||
public void clearPresences() {
|
||||
this.presences.clear();
|
||||
}
|
||||
|
||||
public int getMostAvailableStatus() {
|
||||
int status = OFFLINE;
|
||||
Iterator<Entry<String, Integer>> it = presences.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Entry<String, Integer> entry = it.next();
|
||||
if (entry.getValue() < status)
|
||||
status = entry.getValue();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
public static int parseShow(Element show) {
|
||||
if ((show == null) || (show.getContent() == null)) {
|
||||
return Presences.ONLINE;
|
||||
} else if (show.getContent().equals("away")) {
|
||||
return Presences.AWAY;
|
||||
} else if (show.getContent().equals("xa")) {
|
||||
return Presences.XA;
|
||||
} else if (show.getContent().equals("chat")) {
|
||||
return Presences.CHAT;
|
||||
} else if (show.getContent().equals("dnd")) {
|
||||
return Presences.DND;
|
||||
} else {
|
||||
return Presences.OFFLINE;
|
||||
}
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return presences.size();
|
||||
}
|
||||
|
||||
public String[] asStringArray() {
|
||||
final String[] presencesArray = new String[presences.size()];
|
||||
presences.keySet().toArray(presencesArray);
|
||||
return presencesArray;
|
||||
}
|
||||
|
||||
public boolean has(String presence) {
|
||||
return presences.containsKey(presence);
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package eu.siacs.conversations.entities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class Roster {
|
||||
Account account;
|
||||
ConcurrentHashMap<String, Contact> contacts = new ConcurrentHashMap<String, Contact>();
|
||||
private String version = null;
|
||||
|
||||
public Roster(Account account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public Contact getContactFromRoster(String jid) {
|
||||
if (jid == null) {
|
||||
return null;
|
||||
}
|
||||
String cleanJid = jid.split("/", 2)[0];
|
||||
Contact contact = contacts.get(cleanJid);
|
||||
if (contact != null && contact.showInRoster()) {
|
||||
return contact;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Contact getContact(String jid) {
|
||||
String cleanJid = jid.split("/", 2)[0].toLowerCase(Locale.getDefault());
|
||||
if (contacts.containsKey(cleanJid)) {
|
||||
return contacts.get(cleanJid);
|
||||
} else {
|
||||
Contact contact = new Contact(cleanJid);
|
||||
contact.setAccount(account);
|
||||
contacts.put(cleanJid, contact);
|
||||
return contact;
|
||||
}
|
||||
}
|
||||
|
||||
public void clearPresences() {
|
||||
for (Contact contact : getContacts()) {
|
||||
contact.clearPresences();
|
||||
}
|
||||
}
|
||||
|
||||
public void markAllAsNotInRoster() {
|
||||
for (Contact contact : getContacts()) {
|
||||
contact.resetOption(Contact.Options.IN_ROSTER);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearSystemAccounts() {
|
||||
for (Contact contact : getContacts()) {
|
||||
contact.setPhotoUri(null);
|
||||
contact.setSystemName(null);
|
||||
contact.setSystemAccount(null);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Contact> getContacts() {
|
||||
return new ArrayList<Contact>(this.contacts.values());
|
||||
}
|
||||
|
||||
public void initContact(Contact contact) {
|
||||
contact.setAccount(account);
|
||||
contact.setOption(Contact.Options.IN_ROSTER);
|
||||
contacts.put(contact.getJid(), contact);
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return this.account;
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package eu.siacs.conversations.generator;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
public abstract class AbstractGenerator {
|
||||
public final String[] FEATURES = { "urn:xmpp:jingle:1",
|
||||
"urn:xmpp:jingle:apps:file-transfer:3",
|
||||
"urn:xmpp:jingle:transports:s5b:1",
|
||||
"urn:xmpp:jingle:transports:ibb:1", "urn:xmpp:receipts",
|
||||
"urn:xmpp:chat-markers:0", "http://jabber.org/protocol/muc",
|
||||
"jabber:x:conference", "http://jabber.org/protocol/caps",
|
||||
"http://jabber.org/protocol/disco#info",
|
||||
"urn:xmpp:avatar:metadata+notify" };
|
||||
public final String IDENTITY_NAME = "Conversations 0.7";
|
||||
public final String IDENTITY_TYPE = "phone";
|
||||
|
||||
protected XmppConnectionService mXmppConnectionService;
|
||||
|
||||
protected AbstractGenerator(XmppConnectionService service) {
|
||||
this.mXmppConnectionService = service;
|
||||
}
|
||||
|
||||
public String getCapHash() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("client/" + IDENTITY_TYPE + "//" + IDENTITY_NAME + "<");
|
||||
MessageDigest md = null;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-1");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
List<String> features = Arrays.asList(FEATURES);
|
||||
Collections.sort(features);
|
||||
for (String feature : features) {
|
||||
s.append(feature + "<");
|
||||
}
|
||||
byte[] sha1 = md.digest(s.toString().getBytes());
|
||||
return new String(Base64.encode(sha1, Base64.DEFAULT)).trim();
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package eu.siacs.conversations.generator;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
|
||||
public class IqGenerator extends AbstractGenerator {
|
||||
|
||||
public IqGenerator(XmppConnectionService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
public IqPacket discoResponse(IqPacket request) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE_RESULT);
|
||||
packet.setId(request.getId());
|
||||
packet.setTo(request.getFrom());
|
||||
Element query = packet.addChild("query",
|
||||
"http://jabber.org/protocol/disco#info");
|
||||
query.setAttribute("node", request.query().getAttribute("node"));
|
||||
Element identity = query.addChild("identity");
|
||||
identity.setAttribute("category", "client");
|
||||
identity.setAttribute("type", this.IDENTITY_TYPE);
|
||||
identity.setAttribute("name", IDENTITY_NAME);
|
||||
List<String> features = Arrays.asList(FEATURES);
|
||||
Collections.sort(features);
|
||||
for (String feature : features) {
|
||||
query.addChild("feature").setAttribute("var", feature);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
protected IqPacket publish(String node, Element item) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE_SET);
|
||||
Element pubsub = packet.addChild("pubsub",
|
||||
"http://jabber.org/protocol/pubsub");
|
||||
Element publish = pubsub.addChild("publish");
|
||||
publish.setAttribute("node", node);
|
||||
publish.addChild(item);
|
||||
return packet;
|
||||
}
|
||||
|
||||
protected IqPacket retrieve(String node, Element item) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE_GET);
|
||||
Element pubsub = packet.addChild("pubsub",
|
||||
"http://jabber.org/protocol/pubsub");
|
||||
Element items = pubsub.addChild("items");
|
||||
items.setAttribute("node", node);
|
||||
if (item != null) {
|
||||
items.addChild(item);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket publishAvatar(Avatar avatar) {
|
||||
Element item = new Element("item");
|
||||
item.setAttribute("id", avatar.sha1sum);
|
||||
Element data = item.addChild("data", "urn:xmpp:avatar:data");
|
||||
data.setContent(avatar.image);
|
||||
return publish("urn:xmpp:avatar:data", item);
|
||||
}
|
||||
|
||||
public IqPacket publishAvatarMetadata(Avatar avatar) {
|
||||
Element item = new Element("item");
|
||||
item.setAttribute("id", avatar.sha1sum);
|
||||
Element metadata = item
|
||||
.addChild("metadata", "urn:xmpp:avatar:metadata");
|
||||
Element info = metadata.addChild("info");
|
||||
info.setAttribute("bytes", avatar.size);
|
||||
info.setAttribute("id", avatar.sha1sum);
|
||||
info.setAttribute("height", avatar.height);
|
||||
info.setAttribute("width", avatar.height);
|
||||
info.setAttribute("type", avatar.type);
|
||||
return publish("urn:xmpp:avatar:metadata", item);
|
||||
}
|
||||
|
||||
public IqPacket retrieveAvatar(Avatar avatar) {
|
||||
Element item = new Element("item");
|
||||
item.setAttribute("id", avatar.sha1sum);
|
||||
IqPacket packet = retrieve("urn:xmpp:avatar:data", item);
|
||||
packet.setTo(avatar.owner);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket retrieveAvatarMetaData(String to) {
|
||||
IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null);
|
||||
if (to != null) {
|
||||
packet.setTo(to);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
package eu.siacs.conversations.generator;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import net.java.otr4j.OtrException;
|
||||
import net.java.otr4j.session.Session;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||
|
||||
public class MessageGenerator extends AbstractGenerator {
|
||||
public MessageGenerator(XmppConnectionService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
private MessagePacket preparePacket(Message message, boolean addDelay) {
|
||||
Conversation conversation = message.getConversation();
|
||||
Account account = conversation.getAccount();
|
||||
MessagePacket packet = new MessagePacket();
|
||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||
packet.setTo(message.getCounterpart());
|
||||
packet.setType(MessagePacket.TYPE_CHAT);
|
||||
packet.addChild("markable", "urn:xmpp:chat-markers:0");
|
||||
if (this.mXmppConnectionService.indicateReceived()) {
|
||||
packet.addChild("request", "urn:xmpp:receipts");
|
||||
}
|
||||
} else if (message.getType() == Message.TYPE_PRIVATE) {
|
||||
packet.setTo(message.getCounterpart());
|
||||
packet.setType(MessagePacket.TYPE_CHAT);
|
||||
} else {
|
||||
packet.setTo(message.getCounterpart().split("/", 2)[0]);
|
||||
packet.setType(MessagePacket.TYPE_GROUPCHAT);
|
||||
}
|
||||
packet.setFrom(account.getFullJid());
|
||||
packet.setId(message.getUuid());
|
||||
if (addDelay) {
|
||||
addDelay(packet, message.getTimeSent());
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
private void addDelay(MessagePacket packet, long timestamp) {
|
||||
final SimpleDateFormat mDateFormat = new SimpleDateFormat(
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
|
||||
mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
Element delay = packet.addChild("delay", "urn:xmpp:delay");
|
||||
Date date = new Date(timestamp);
|
||||
delay.setAttribute("stamp", mDateFormat.format(date));
|
||||
}
|
||||
|
||||
public MessagePacket generateOtrChat(Message message) {
|
||||
return generateOtrChat(message, false);
|
||||
}
|
||||
|
||||
public MessagePacket generateOtrChat(Message message, boolean addDelay) {
|
||||
Session otrSession = message.getConversation().getOtrSession();
|
||||
if (otrSession == null) {
|
||||
return null;
|
||||
}
|
||||
MessagePacket packet = preparePacket(message, addDelay);
|
||||
packet.addChild("private", "urn:xmpp:carbons:2");
|
||||
packet.addChild("no-copy", "urn:xmpp:hints");
|
||||
try {
|
||||
packet.setBody(otrSession.transformSending(message.getBody()));
|
||||
return packet;
|
||||
} catch (OtrException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public MessagePacket generateChat(Message message) {
|
||||
return generateChat(message, false);
|
||||
}
|
||||
|
||||
public MessagePacket generateChat(Message message, boolean addDelay) {
|
||||
MessagePacket packet = preparePacket(message, addDelay);
|
||||
packet.setBody(message.getBody());
|
||||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket generatePgpChat(Message message) {
|
||||
return generatePgpChat(message, false);
|
||||
}
|
||||
|
||||
public MessagePacket generatePgpChat(Message message, boolean addDelay) {
|
||||
MessagePacket packet = preparePacket(message, addDelay);
|
||||
packet.setBody("This is an XEP-0027 encryted message");
|
||||
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
|
||||
packet.addChild("x", "jabber:x:encrypted").setContent(
|
||||
message.getEncryptedBody());
|
||||
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||
packet.addChild("x", "jabber:x:encrypted").setContent(
|
||||
message.getBody());
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket generateNotAcceptable(MessagePacket origin) {
|
||||
MessagePacket packet = generateError(origin);
|
||||
Element error = packet.addChild("error");
|
||||
error.setAttribute("type", "modify");
|
||||
error.setAttribute("code", "406");
|
||||
error.addChild("not-acceptable");
|
||||
return packet;
|
||||
}
|
||||
|
||||
private MessagePacket generateError(MessagePacket origin) {
|
||||
MessagePacket packet = new MessagePacket();
|
||||
packet.setId(origin.getId());
|
||||
packet.setTo(origin.getFrom());
|
||||
packet.setBody(origin.getBody());
|
||||
packet.setType(MessagePacket.TYPE_ERROR);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket confirm(Account account, String to, String id) {
|
||||
MessagePacket packet = new MessagePacket();
|
||||
packet.setType(MessagePacket.TYPE_NORMAL);
|
||||
packet.setTo(to);
|
||||
packet.setFrom(account.getFullJid());
|
||||
Element received = packet.addChild("displayed",
|
||||
"urn:xmpp:chat-markers:0");
|
||||
received.setAttribute("id", id);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket conferenceSubject(Conversation conversation,
|
||||
String subject) {
|
||||
MessagePacket packet = new MessagePacket();
|
||||
packet.setType(MessagePacket.TYPE_GROUPCHAT);
|
||||
packet.setTo(conversation.getContactJid().split("/", 2)[0]);
|
||||
Element subjectChild = new Element("subject");
|
||||
subjectChild.setContent(subject);
|
||||
packet.addChild(subjectChild);
|
||||
packet.setFrom(conversation.getAccount().getJid());
|
||||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket directInvite(Conversation conversation, String contact) {
|
||||
MessagePacket packet = new MessagePacket();
|
||||
packet.setType(MessagePacket.TYPE_NORMAL);
|
||||
packet.setTo(contact);
|
||||
packet.setFrom(conversation.getAccount().getFullJid());
|
||||
Element x = packet.addChild("x", "jabber:x:conference");
|
||||
x.setAttribute("jid", conversation.getContactJid().split("/", 2)[0]);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket invite(Conversation conversation, String contact) {
|
||||
MessagePacket packet = new MessagePacket();
|
||||
packet.setTo(conversation.getContactJid().split("/", 2)[0]);
|
||||
packet.setFrom(conversation.getAccount().getFullJid());
|
||||
Element x = new Element("x");
|
||||
x.setAttribute("xmlns", "http://jabber.org/protocol/muc#user");
|
||||
Element invite = new Element("invite");
|
||||
invite.setAttribute("to", contact);
|
||||
x.addChild(invite);
|
||||
packet.addChild(x);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket received(Account account,
|
||||
MessagePacket originalMessage, String namespace) {
|
||||
MessagePacket receivedPacket = new MessagePacket();
|
||||
receivedPacket.setType(MessagePacket.TYPE_NORMAL);
|
||||
receivedPacket.setTo(originalMessage.getFrom());
|
||||
receivedPacket.setFrom(account.getFullJid());
|
||||
Element received = receivedPacket.addChild("received", namespace);
|
||||
received.setAttribute("id", originalMessage.getId());
|
||||
return receivedPacket;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package eu.siacs.conversations.generator;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
||||
|
||||
public class PresenceGenerator extends AbstractGenerator {
|
||||
|
||||
public PresenceGenerator(XmppConnectionService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
private PresencePacket subscription(String type, Contact contact) {
|
||||
PresencePacket packet = new PresencePacket();
|
||||
packet.setAttribute("type", type);
|
||||
packet.setAttribute("to", contact.getJid());
|
||||
packet.setAttribute("from", contact.getAccount().getJid());
|
||||
return packet;
|
||||
}
|
||||
|
||||
public PresencePacket requestPresenceUpdatesFrom(Contact contact) {
|
||||
return subscription("subscribe", contact);
|
||||
}
|
||||
|
||||
public PresencePacket stopPresenceUpdatesFrom(Contact contact) {
|
||||
return subscription("unsubscribe", contact);
|
||||
}
|
||||
|
||||
public PresencePacket stopPresenceUpdatesTo(Contact contact) {
|
||||
return subscription("unsubscribed", contact);
|
||||
}
|
||||
|
||||
public PresencePacket sendPresenceUpdatesTo(Contact contact) {
|
||||
return subscription("subscribed", contact);
|
||||
}
|
||||
|
||||
public PresencePacket sendPresence(Account account) {
|
||||
PresencePacket packet = new PresencePacket();
|
||||
packet.setAttribute("from", account.getFullJid());
|
||||
String sig = account.getPgpSignature();
|
||||
if (sig != null) {
|
||||
packet.addChild("status").setContent("online");
|
||||
packet.addChild("x", "jabber:x:signed").setContent(sig);
|
||||
}
|
||||
String capHash = getCapHash();
|
||||
if (capHash != null) {
|
||||
Element cap = packet.addChild("c",
|
||||
"http://jabber.org/protocol/caps");
|
||||
cap.setAttribute("hash", "sha-1");
|
||||
cap.setAttribute("node", "http://conversions.siacs.eu");
|
||||
cap.setAttribute("ver", capHash);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
}
|
@ -0,0 +1,255 @@
|
||||
package eu.siacs.conversations.http;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Downloadable;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
|
||||
public class HttpConnection implements Downloadable {
|
||||
|
||||
private HttpConnectionManager mHttpConnectionManager;
|
||||
private XmppConnectionService mXmppConnectionService;
|
||||
|
||||
private URL mUrl;
|
||||
private Message message;
|
||||
private DownloadableFile file;
|
||||
private int mStatus = Downloadable.STATUS_UNKNOWN;
|
||||
|
||||
public HttpConnection(HttpConnectionManager manager) {
|
||||
this.mHttpConnectionManager = manager;
|
||||
this.mXmppConnectionService = manager.getXmppConnectionService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
if (mXmppConnectionService.hasInternetConnection()) {
|
||||
if (this.mStatus == STATUS_OFFER_CHECK_FILESIZE) {
|
||||
checkFileSize(true);
|
||||
} else {
|
||||
new Thread(new FileDownloader(true)).start();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void init(Message message) {
|
||||
this.message = message;
|
||||
this.message.setDownloadable(this);
|
||||
try {
|
||||
mUrl = new URL(message.getBody());
|
||||
this.file = mXmppConnectionService.getFileBackend().getFile(
|
||||
message, false);
|
||||
String reference = mUrl.getRef();
|
||||
if (reference != null && reference.length() == 96) {
|
||||
this.file.setKey(CryptoHelper.hexToBytes(reference));
|
||||
}
|
||||
if (this.message.getEncryption() == Message.ENCRYPTION_OTR
|
||||
&& this.file.getKey() == null) {
|
||||
this.message.setEncryption(Message.ENCRYPTION_NONE);
|
||||
}
|
||||
checkFileSize(false);
|
||||
} catch (MalformedURLException e) {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFileSize(boolean interactive) {
|
||||
new Thread(new FileSizeChecker(interactive)).start();
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
mHttpConnectionManager.finishConnection(this);
|
||||
message.setDownloadable(null);
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||
intent.setData(Uri.fromFile(file));
|
||||
mXmppConnectionService.sendBroadcast(intent);
|
||||
message.setDownloadable(null);
|
||||
mHttpConnectionManager.finishConnection(this);
|
||||
}
|
||||
|
||||
private void changeStatus(int status) {
|
||||
this.mStatus = status;
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
}
|
||||
|
||||
private void setupTrustManager(HttpsURLConnection connection,
|
||||
boolean interactive) {
|
||||
X509TrustManager trustManager;
|
||||
HostnameVerifier hostnameVerifier;
|
||||
if (interactive) {
|
||||
trustManager = mXmppConnectionService.getMemorizingTrustManager();
|
||||
hostnameVerifier = mXmppConnectionService
|
||||
.getMemorizingTrustManager().wrapHostnameVerifier(
|
||||
new StrictHostnameVerifier());
|
||||
} else {
|
||||
trustManager = mXmppConnectionService.getMemorizingTrustManager()
|
||||
.getNonInteractive();
|
||||
hostnameVerifier = mXmppConnectionService
|
||||
.getMemorizingTrustManager()
|
||||
.wrapHostnameVerifierNonInteractive(
|
||||
new StrictHostnameVerifier());
|
||||
}
|
||||
try {
|
||||
SSLContext sc = SSLContext.getInstance("TLS");
|
||||
sc.init(null, new X509TrustManager[] { trustManager },
|
||||
mXmppConnectionService.getRNG());
|
||||
connection.setSSLSocketFactory(sc.getSocketFactory());
|
||||
connection.setHostnameVerifier(hostnameVerifier);
|
||||
} catch (KeyManagementException e) {
|
||||
return;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private class FileSizeChecker implements Runnable {
|
||||
|
||||
private boolean interactive = false;
|
||||
|
||||
public FileSizeChecker(boolean interactive) {
|
||||
this.interactive = interactive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long size;
|
||||
try {
|
||||
size = retrieveFileSize();
|
||||
} catch (SSLHandshakeException e) {
|
||||
changeStatus(STATUS_OFFER_CHECK_FILESIZE);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
file.setExpectedSize(size);
|
||||
if (size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
|
||||
new Thread(new FileDownloader(interactive)).start();
|
||||
} else {
|
||||
changeStatus(STATUS_OFFER);
|
||||
}
|
||||
}
|
||||
|
||||
private long retrieveFileSize() throws IOException,
|
||||
SSLHandshakeException {
|
||||
changeStatus(STATUS_CHECKING);
|
||||
HttpURLConnection connection = (HttpURLConnection) mUrl
|
||||
.openConnection();
|
||||
connection.setRequestMethod("HEAD");
|
||||
if (connection instanceof HttpsURLConnection) {
|
||||
setupTrustManager((HttpsURLConnection) connection, interactive);
|
||||
}
|
||||
connection.connect();
|
||||
String contentLength = connection.getHeaderField("Content-Length");
|
||||
if (contentLength == null) {
|
||||
throw new IOException();
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(contentLength, 10);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class FileDownloader implements Runnable {
|
||||
|
||||
private boolean interactive = false;
|
||||
|
||||
public FileDownloader(boolean interactive) {
|
||||
this.interactive = interactive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
changeStatus(STATUS_DOWNLOADING);
|
||||
download();
|
||||
updateImageBounds();
|
||||
finish();
|
||||
} catch (SSLHandshakeException e) {
|
||||
changeStatus(STATUS_OFFER);
|
||||
} catch (IOException e) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void download() throws SSLHandshakeException, IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) mUrl
|
||||
.openConnection();
|
||||
if (connection instanceof HttpsURLConnection) {
|
||||
setupTrustManager((HttpsURLConnection) connection, interactive);
|
||||
}
|
||||
connection.connect();
|
||||
BufferedInputStream is = new BufferedInputStream(
|
||||
connection.getInputStream());
|
||||
OutputStream os = file.createOutputStream();
|
||||
int count = -1;
|
||||
byte[] buffer = new byte[1024];
|
||||
while ((count = is.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, count);
|
||||
}
|
||||
os.flush();
|
||||
os.close();
|
||||
is.close();
|
||||
}
|
||||
|
||||
private void updateImageBounds() {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
int imageHeight = options.outHeight;
|
||||
int imageWidth = options.outWidth;
|
||||
message.setBody(mUrl.toString() + "," + file.getSize() + ','
|
||||
+ imageWidth + ',' + imageHeight);
|
||||
message.setType(Message.TYPE_IMAGE);
|
||||
mXmppConnectionService.updateMessage(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatus() {
|
||||
return this.mStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFileSize() {
|
||||
if (this.file != null) {
|
||||
return this.file.getExpectedSize();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package eu.siacs.conversations.http;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.AbstractConnectionManager;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
|
||||
public class HttpConnectionManager extends AbstractConnectionManager {
|
||||
|
||||
public HttpConnectionManager(XmppConnectionService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
private List<HttpConnection> connections = new CopyOnWriteArrayList<HttpConnection>();
|
||||
|
||||
public HttpConnection createNewConnection(Message message) {
|
||||
HttpConnection connection = new HttpConnection(this);
|
||||
connection.init(message);
|
||||
this.connections.add(connection);
|
||||
return connection;
|
||||
}
|
||||
|
||||
public void finishConnection(HttpConnection connection) {
|
||||
this.connections.remove(connection);
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package eu.siacs.conversations.parser;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
|
||||
public abstract class AbstractParser {
|
||||
|
||||
protected XmppConnectionService mXmppConnectionService;
|
||||
|
||||
protected AbstractParser(XmppConnectionService service) {
|
||||
this.mXmppConnectionService = service;
|
||||
}
|
||||
|
||||
protected long getTimestamp(Element packet) {
|
||||
long now = System.currentTimeMillis();
|
||||
ArrayList<String> stamps = new ArrayList<String>();
|
||||
for (Element child : packet.getChildren()) {
|
||||
if (child.getName().equals("delay")) {
|
||||
stamps.add(child.getAttribute("stamp").replace("Z", "+0000"));
|
||||
}
|
||||
}
|
||||
Collections.sort(stamps);
|
||||
if (stamps.size() >= 1) {
|
||||
try {
|
||||
String stamp = stamps.get(stamps.size() - 1);
|
||||
if (stamp.contains(".")) {
|
||||
Date date = new SimpleDateFormat(
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US)
|
||||
.parse(stamp);
|
||||
if (now < date.getTime()) {
|
||||
return now;
|
||||
} else {
|
||||
return date.getTime();
|
||||
}
|
||||
} else {
|
||||
Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",
|
||||
Locale.US).parse(stamp);
|
||||
if (now < date.getTime()) {
|
||||
return now;
|
||||
} else {
|
||||
return date.getTime();
|
||||
}
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
return now;
|
||||
}
|
||||
} else {
|
||||
return now;
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateLastseen(Element packet, Account account,
|
||||
boolean presenceOverwrite) {
|
||||
String[] fromParts = packet.getAttribute("from").split("/", 2);
|
||||
String from = fromParts[0];
|
||||
String presence = null;
|
||||
if (fromParts.length >= 2) {
|
||||
presence = fromParts[1];
|
||||
} else {
|
||||
presence = "";
|
||||
}
|
||||
Contact contact = account.getRoster().getContact(from);
|
||||
long timestamp = getTimestamp(packet);
|
||||
if (timestamp >= contact.lastseen.time) {
|
||||
contact.lastseen.time = timestamp;
|
||||
if ((presence != null) && (presenceOverwrite)) {
|
||||
contact.lastseen.presence = presence;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected String avatarData(Element items) {
|
||||
Element item = items.findChild("item");
|
||||
if (item == null) {
|
||||
return null;
|
||||
}
|
||||
Element data = item.findChild("data", "urn:xmpp:avatar:data");
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
return data.getContent();
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package eu.siacs.conversations.parser;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
|
||||
public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||
|
||||
public IqParser(XmppConnectionService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
public void rosterItems(Account account, Element query) {
|
||||
String version = query.getAttribute("ver");
|
||||
if (version != null) {
|
||||
account.getRoster().setVersion(version);
|
||||
}
|
||||
for (Element item : query.getChildren()) {
|
||||
if (item.getName().equals("item")) {
|
||||
String jid = item.getAttribute("jid");
|
||||
String name = item.getAttribute("name");
|
||||
String subscription = item.getAttribute("subscription");
|
||||
Contact contact = account.getRoster().getContact(jid);
|
||||
if (!contact.getOption(Contact.Options.DIRTY_PUSH)) {
|
||||
contact.setServerName(name);
|
||||
}
|
||||
if (subscription != null) {
|
||||
if (subscription.equals("remove")) {
|
||||
contact.resetOption(Contact.Options.IN_ROSTER);
|
||||
contact.resetOption(Contact.Options.DIRTY_DELETE);
|
||||
contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
|
||||
} else {
|
||||
contact.setOption(Contact.Options.IN_ROSTER);
|
||||
contact.resetOption(Contact.Options.DIRTY_PUSH);
|
||||
contact.parseSubscriptionFromElement(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mXmppConnectionService.updateRosterUi();
|
||||
}
|
||||
|
||||
public String avatarData(IqPacket packet) {
|
||||
Element pubsub = packet.findChild("pubsub",
|
||||
"http://jabber.org/protocol/pubsub");
|
||||
if (pubsub == null) {
|
||||
return null;
|
||||
}
|
||||
Element items = pubsub.findChild("items");
|
||||
if (items == null) {
|
||||
return null;
|
||||
}
|
||||
return super.avatarData(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
||||
if (packet.hasChild("query", "jabber:iq:roster")) {
|
||||
String from = packet.getFrom();
|
||||
if ((from == null) || (from.equals(account.getJid()))) {
|
||||
Element query = packet.findChild("query");
|
||||
this.rosterItems(account, query);
|
||||
}
|
||||
} else if (packet.hasChild("open", "http://jabber.org/protocol/ibb")
|
||||
|| packet.hasChild("data", "http://jabber.org/protocol/ibb")) {
|
||||
mXmppConnectionService.getJingleConnectionManager()
|
||||
.deliverIbbPacket(account, packet);
|
||||
} else if (packet.hasChild("query",
|
||||
"http://jabber.org/protocol/disco#info")) {
|
||||
IqPacket response = mXmppConnectionService.getIqGenerator()
|
||||
.discoResponse(packet);
|
||||
account.getXmppConnection().sendIqPacket(response, null);
|
||||
} else if (packet.hasChild("ping", "urn:xmpp:ping")) {
|
||||
IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
|
||||
mXmppConnectionService.sendIqPacket(account, response, null);
|
||||
} else {
|
||||
if ((packet.getType() == IqPacket.TYPE_GET)
|
||||
|| (packet.getType() == IqPacket.TYPE_SET)) {
|
||||
IqPacket response = packet.generateRespone(IqPacket.TYPE_ERROR);
|
||||
Element error = response.addChild("error");
|
||||
error.setAttribute("type", "cancel");
|
||||
error.addChild("feature-not-implemented",
|
||||
"urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||
account.getXmppConnection().sendIqPacket(response, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,517 @@
|
||||
package eu.siacs.conversations.parser;
|
||||
|
||||
import net.java.otr4j.session.Session;
|
||||
import net.java.otr4j.session.SessionStatus;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.NotificationService;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
|
||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||
|
||||
public class MessageParser extends AbstractParser implements
|
||||
OnMessagePacketReceived {
|
||||
public MessageParser(XmppConnectionService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
private Message parseChat(MessagePacket packet, Account account) {
|
||||
String[] fromParts = packet.getFrom().split("/", 2);
|
||||
Conversation conversation = mXmppConnectionService
|
||||
.findOrCreateConversation(account, fromParts[0], false);
|
||||
updateLastseen(packet, account, true);
|
||||
String pgpBody = getPgpBody(packet);
|
||||
Message finishedMessage;
|
||||
if (pgpBody != null) {
|
||||
finishedMessage = new Message(conversation, packet.getFrom(),
|
||||
pgpBody, Message.ENCRYPTION_PGP, Message.STATUS_RECEIVED);
|
||||
} else {
|
||||
finishedMessage = new Message(conversation, packet.getFrom(),
|
||||
packet.getBody(), Message.ENCRYPTION_NONE,
|
||||
Message.STATUS_RECEIVED);
|
||||
}
|
||||
finishedMessage.setRemoteMsgId(packet.getId());
|
||||
finishedMessage.markable = isMarkable(packet);
|
||||
if (conversation.getMode() == Conversation.MODE_MULTI
|
||||
&& fromParts.length >= 2) {
|
||||
finishedMessage.setType(Message.TYPE_PRIVATE);
|
||||
finishedMessage.setPresence(fromParts[1]);
|
||||
finishedMessage.setTrueCounterpart(conversation.getMucOptions()
|
||||
.getTrueCounterpart(fromParts[1]));
|
||||
if (conversation.hasDuplicateMessage(finishedMessage)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
finishedMessage.setTime(getTimestamp(packet));
|
||||
return finishedMessage;
|
||||
}
|
||||
|
||||
private Message parseOtrChat(MessagePacket packet, Account account) {
|
||||
boolean properlyAddressed = (packet.getTo().split("/", 2).length == 2)
|
||||
|| (account.countPresences() == 1);
|
||||
String[] fromParts = packet.getFrom().split("/", 2);
|
||||
Conversation conversation = mXmppConnectionService
|
||||
.findOrCreateConversation(account, fromParts[0], false);
|
||||
String presence;
|
||||
if (fromParts.length >= 2) {
|
||||
presence = fromParts[1];
|
||||
} else {
|
||||
presence = "";
|
||||
}
|
||||
updateLastseen(packet, account, true);
|
||||
String body = packet.getBody();
|
||||
if (body.matches("^\\?OTRv\\d*\\?")) {
|
||||
conversation.endOtrIfNeeded();
|
||||
}
|
||||
if (!conversation.hasValidOtrSession()) {
|
||||
if (properlyAddressed) {
|
||||
conversation.startOtrSession(mXmppConnectionService, presence,
|
||||
false);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
String foreignPresence = conversation.getOtrSession()
|
||||
.getSessionID().getUserID();
|
||||
if (!foreignPresence.equals(presence)) {
|
||||
conversation.endOtrIfNeeded();
|
||||
if (properlyAddressed) {
|
||||
conversation.startOtrSession(mXmppConnectionService,
|
||||
presence, false);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
Session otrSession = conversation.getOtrSession();
|
||||
SessionStatus before = otrSession.getSessionStatus();
|
||||
body = otrSession.transformReceiving(body);
|
||||
SessionStatus after = otrSession.getSessionStatus();
|
||||
if ((before != after) && (after == SessionStatus.ENCRYPTED)) {
|
||||
mXmppConnectionService.onOtrSessionEstablished(conversation);
|
||||
} else if ((before != after) && (after == SessionStatus.FINISHED)) {
|
||||
conversation.resetOtrSession();
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
}
|
||||
if ((body == null) || (body.isEmpty())) {
|
||||
return null;
|
||||
}
|
||||
if (body.startsWith(CryptoHelper.FILETRANSFER)) {
|
||||
String key = body.substring(CryptoHelper.FILETRANSFER.length());
|
||||
conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
|
||||
return null;
|
||||
}
|
||||
Message finishedMessage = new Message(conversation,
|
||||
packet.getFrom(), body, Message.ENCRYPTION_OTR,
|
||||
Message.STATUS_RECEIVED);
|
||||
finishedMessage.setTime(getTimestamp(packet));
|
||||
finishedMessage.setRemoteMsgId(packet.getId());
|
||||
finishedMessage.markable = isMarkable(packet);
|
||||
return finishedMessage;
|
||||
} catch (Exception e) {
|
||||
String receivedId = packet.getId();
|
||||
if (receivedId != null) {
|
||||
mXmppConnectionService.replyWithNotAcceptable(account, packet);
|
||||
}
|
||||
conversation.resetOtrSession();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Message parseGroupchat(MessagePacket packet, Account account) {
|
||||
int status;
|
||||
String[] fromParts = packet.getFrom().split("/", 2);
|
||||
if (mXmppConnectionService.find(account.pendingConferenceLeaves,
|
||||
account, fromParts[0]) != null) {
|
||||
return null;
|
||||
}
|
||||
Conversation conversation = mXmppConnectionService
|
||||
.findOrCreateConversation(account, fromParts[0], true);
|
||||
if (packet.hasChild("subject")) {
|
||||
conversation.getMucOptions().setSubject(
|
||||
packet.findChild("subject").getContent());
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
return null;
|
||||
}
|
||||
if ((fromParts.length == 1)) {
|
||||
return null;
|
||||
}
|
||||
String counterPart = fromParts[1];
|
||||
if (counterPart.equals(conversation.getMucOptions().getActualNick())) {
|
||||
if (mXmppConnectionService.markMessage(conversation,
|
||||
packet.getId(), Message.STATUS_SEND)) {
|
||||
return null;
|
||||
} else {
|
||||
status = Message.STATUS_SEND;
|
||||
}
|
||||
} else {
|
||||
status = Message.STATUS_RECEIVED;
|
||||
}
|
||||
String pgpBody = getPgpBody(packet);
|
||||
Message finishedMessage;
|
||||
if (pgpBody == null) {
|
||||
finishedMessage = new Message(conversation, counterPart,
|
||||
packet.getBody(), Message.ENCRYPTION_NONE, status);
|
||||
} else {
|
||||
finishedMessage = new Message(conversation, counterPart, pgpBody,
|
||||
Message.ENCRYPTION_PGP, status);
|
||||
}
|
||||
finishedMessage.setRemoteMsgId(packet.getId());
|
||||
finishedMessage.markable = isMarkable(packet);
|
||||
if (status == Message.STATUS_RECEIVED) {
|
||||
finishedMessage.setTrueCounterpart(conversation.getMucOptions()
|
||||
.getTrueCounterpart(counterPart));
|
||||
}
|
||||
if (packet.hasChild("delay")
|
||||
&& conversation.hasDuplicateMessage(finishedMessage)) {
|
||||
return null;
|
||||
}
|
||||
finishedMessage.setTime(getTimestamp(packet));
|
||||
return finishedMessage;
|
||||
}
|
||||
|
||||
private Message parseCarbonMessage(MessagePacket packet, Account account) {
|
||||
int status;
|
||||
String fullJid;
|
||||
Element forwarded;
|
||||
if (packet.hasChild("received", "urn:xmpp:carbons:2")) {
|
||||
forwarded = packet.findChild("received", "urn:xmpp:carbons:2")
|
||||
.findChild("forwarded", "urn:xmpp:forward:0");
|
||||
status = Message.STATUS_RECEIVED;
|
||||
} else if (packet.hasChild("sent", "urn:xmpp:carbons:2")) {
|
||||
forwarded = packet.findChild("sent", "urn:xmpp:carbons:2")
|
||||
.findChild("forwarded", "urn:xmpp:forward:0");
|
||||
status = Message.STATUS_SEND;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if (forwarded == null) {
|
||||
return null;
|
||||
}
|
||||
Element message = forwarded.findChild("message");
|
||||
if (message == null) {
|
||||
return null;
|
||||
}
|
||||
if (!message.hasChild("body")) {
|
||||
if (status == Message.STATUS_RECEIVED
|
||||
&& message.getAttribute("from") != null) {
|
||||
parseNonMessage(message, account);
|
||||
} else if (status == Message.STATUS_SEND
|
||||
&& message.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
|
||||
String to = message.getAttribute("to");
|
||||
if (to != null) {
|
||||
Conversation conversation = mXmppConnectionService.find(
|
||||
mXmppConnectionService.getConversations(), account,
|
||||
to.split("/")[0]);
|
||||
if (conversation != null) {
|
||||
mXmppConnectionService.markRead(conversation, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (status == Message.STATUS_RECEIVED) {
|
||||
fullJid = message.getAttribute("from");
|
||||
if (fullJid == null) {
|
||||
return null;
|
||||
} else {
|
||||
updateLastseen(message, account, true);
|
||||
}
|
||||
} else {
|
||||
fullJid = message.getAttribute("to");
|
||||
if (fullJid == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
String[] parts = fullJid.split("/", 2);
|
||||
Conversation conversation = mXmppConnectionService
|
||||
.findOrCreateConversation(account, parts[0], false);
|
||||
String pgpBody = getPgpBody(message);
|
||||
Message finishedMessage;
|
||||
if (pgpBody != null) {
|
||||
finishedMessage = new Message(conversation, fullJid, pgpBody,
|
||||
Message.ENCRYPTION_PGP, status);
|
||||
} else {
|
||||
String body = message.findChild("body").getContent();
|
||||
finishedMessage = new Message(conversation, fullJid, body,
|
||||
Message.ENCRYPTION_NONE, status);
|
||||
}
|
||||
finishedMessage.setTime(getTimestamp(message));
|
||||
finishedMessage.setRemoteMsgId(message.getAttribute("id"));
|
||||
finishedMessage.markable = isMarkable(message);
|
||||
if (conversation.getMode() == Conversation.MODE_MULTI
|
||||
&& parts.length >= 2) {
|
||||
finishedMessage.setType(Message.TYPE_PRIVATE);
|
||||
finishedMessage.setPresence(parts[1]);
|
||||
finishedMessage.setTrueCounterpart(conversation.getMucOptions()
|
||||
.getTrueCounterpart(parts[1]));
|
||||
if (conversation.hasDuplicateMessage(finishedMessage)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return finishedMessage;
|
||||
}
|
||||
|
||||
private void parseError(MessagePacket packet, Account account) {
|
||||
String[] fromParts = packet.getFrom().split("/", 2);
|
||||
mXmppConnectionService.markMessage(account, fromParts[0],
|
||||
packet.getId(), Message.STATUS_SEND_FAILED);
|
||||
}
|
||||
|
||||
private void parseNonMessage(Element packet, Account account) {
|
||||
String from = packet.getAttribute("from");
|
||||
if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
|
||||
Element event = packet.findChild("event",
|
||||
"http://jabber.org/protocol/pubsub#event");
|
||||
parseEvent(event, packet.getAttribute("from"), account);
|
||||
} else if (from != null
|
||||
&& packet.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
|
||||
String id = packet
|
||||
.findChild("displayed", "urn:xmpp:chat-markers:0")
|
||||
.getAttribute("id");
|
||||
updateLastseen(packet, account, true);
|
||||
mXmppConnectionService.markMessage(account, from.split("/", 2)[0],
|
||||
id, Message.STATUS_SEND_DISPLAYED);
|
||||
} else if (from != null
|
||||
&& packet.hasChild("received", "urn:xmpp:chat-markers:0")) {
|
||||
String id = packet.findChild("received", "urn:xmpp:chat-markers:0")
|
||||
.getAttribute("id");
|
||||
updateLastseen(packet, account, false);
|
||||
mXmppConnectionService.markMessage(account, from.split("/", 2)[0],
|
||||
id, Message.STATUS_SEND_RECEIVED);
|
||||
} else if (from != null
|
||||
&& packet.hasChild("received", "urn:xmpp:receipts")) {
|
||||
String id = packet.findChild("received", "urn:xmpp:receipts")
|
||||
.getAttribute("id");
|
||||
updateLastseen(packet, account, false);
|
||||
mXmppConnectionService.markMessage(account, from.split("/", 2)[0],
|
||||
id, Message.STATUS_SEND_RECEIVED);
|
||||
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) {
|
||||
Element x = packet.findChild("x",
|
||||
"http://jabber.org/protocol/muc#user");
|
||||
if (x.hasChild("invite")) {
|
||||
Conversation conversation = mXmppConnectionService
|
||||
.findOrCreateConversation(account,
|
||||
packet.getAttribute("from"), true);
|
||||
if (!conversation.getMucOptions().online()) {
|
||||
if (x.hasChild("password")) {
|
||||
Element password = x.findChild("password");
|
||||
conversation.getMucOptions().setPassword(
|
||||
password.getContent());
|
||||
mXmppConnectionService.databaseBackend
|
||||
.updateConversation(conversation);
|
||||
}
|
||||
mXmppConnectionService.joinMuc(conversation);
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
}
|
||||
}
|
||||
} else if (packet.hasChild("x", "jabber:x:conference")) {
|
||||
Element x = packet.findChild("x", "jabber:x:conference");
|
||||
String jid = x.getAttribute("jid");
|
||||
String password = x.getAttribute("password");
|
||||
if (jid != null) {
|
||||
Conversation conversation = mXmppConnectionService
|
||||
.findOrCreateConversation(account, jid, true);
|
||||
if (!conversation.getMucOptions().online()) {
|
||||
if (password != null) {
|
||||
conversation.getMucOptions().setPassword(password);
|
||||
mXmppConnectionService.databaseBackend
|
||||
.updateConversation(conversation);
|
||||
}
|
||||
mXmppConnectionService.joinMuc(conversation);
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseEvent(Element event, String from, Account account) {
|
||||
Element items = event.findChild("items");
|
||||
String node = items.getAttribute("node");
|
||||
if (node != null) {
|
||||
if (node.equals("urn:xmpp:avatar:metadata")) {
|
||||
Avatar avatar = Avatar.parseMetadata(items);
|
||||
if (avatar != null) {
|
||||
avatar.owner = from;
|
||||
if (mXmppConnectionService.getFileBackend().isAvatarCached(
|
||||
avatar)) {
|
||||
if (account.getJid().equals(from)) {
|
||||
if (account.setAvatar(avatar.getFilename())) {
|
||||
mXmppConnectionService.databaseBackend
|
||||
.updateAccount(account);
|
||||
}
|
||||
mXmppConnectionService.getAvatarService().clear(
|
||||
account);
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
mXmppConnectionService.updateAccountUi();
|
||||
} else {
|
||||
Contact contact = account.getRoster().getContact(
|
||||
from);
|
||||
contact.setAvatar(avatar.getFilename());
|
||||
mXmppConnectionService.getAvatarService().clear(
|
||||
contact);
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
mXmppConnectionService.updateRosterUi();
|
||||
}
|
||||
} else {
|
||||
mXmppConnectionService.fetchAvatar(account, avatar);
|
||||
}
|
||||
}
|
||||
} else if (node.equals("http://jabber.org/protocol/nick")) {
|
||||
Element item = items.findChild("item");
|
||||
if (item != null) {
|
||||
Element nick = item.findChild("nick",
|
||||
"http://jabber.org/protocol/nick");
|
||||
if (nick != null) {
|
||||
if (from != null) {
|
||||
Contact contact = account.getRoster().getContact(
|
||||
from);
|
||||
contact.setPresenceName(nick.getContent());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getPgpBody(Element message) {
|
||||
Element child = message.findChild("x", "jabber:x:encrypted");
|
||||
if (child == null) {
|
||||
return null;
|
||||
} else {
|
||||
return child.getContent();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMarkable(Element message) {
|
||||
return message.hasChild("markable", "urn:xmpp:chat-markers:0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessagePacketReceived(Account account, MessagePacket packet) {
|
||||
Message message = null;
|
||||
boolean notify = mXmppConnectionService.getPreferences().getBoolean(
|
||||
"show_notification", true);
|
||||
boolean alwaysNotifyInConference = notify
|
||||
&& mXmppConnectionService.getPreferences().getBoolean(
|
||||
"always_notify_in_conference", false);
|
||||
|
||||
this.parseNick(packet, account);
|
||||
|
||||
if ((packet.getType() == MessagePacket.TYPE_CHAT || packet.getType() == MessagePacket.TYPE_NORMAL)) {
|
||||
if ((packet.getBody() != null)
|
||||
&& (packet.getBody().startsWith("?OTR"))) {
|
||||
message = this.parseOtrChat(packet, account);
|
||||
if (message != null) {
|
||||
message.markUnread();
|
||||
}
|
||||
} else if (packet.hasChild("body")
|
||||
&& !(packet.hasChild("x",
|
||||
"http://jabber.org/protocol/muc#user"))) {
|
||||
message = this.parseChat(packet, account);
|
||||
if (message != null) {
|
||||
message.markUnread();
|
||||
}
|
||||
} else if (packet.hasChild("received", "urn:xmpp:carbons:2")
|
||||
|| (packet.hasChild("sent", "urn:xmpp:carbons:2"))) {
|
||||
message = this.parseCarbonMessage(packet, account);
|
||||
if (message != null) {
|
||||
if (message.getStatus() == Message.STATUS_SEND) {
|
||||
account.activateGracePeriod();
|
||||
notify = false;
|
||||
mXmppConnectionService.markRead(
|
||||
message.getConversation(), false);
|
||||
} else {
|
||||
message.markUnread();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parseNonMessage(packet, account);
|
||||
}
|
||||
} else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
|
||||
message = this.parseGroupchat(packet, account);
|
||||
if (message != null) {
|
||||
if (message.getStatus() == Message.STATUS_RECEIVED) {
|
||||
message.markUnread();
|
||||
notify = alwaysNotifyInConference
|
||||
|| NotificationService
|
||||
.wasHighlightedOrPrivate(message);
|
||||
} else {
|
||||
mXmppConnectionService.markRead(message.getConversation(),
|
||||
false);
|
||||
account.activateGracePeriod();
|
||||
notify = false;
|
||||
}
|
||||
}
|
||||
} else if (packet.getType() == MessagePacket.TYPE_ERROR) {
|
||||
this.parseError(packet, account);
|
||||
return;
|
||||
} else if (packet.getType() == MessagePacket.TYPE_HEADLINE) {
|
||||
this.parseHeadline(packet, account);
|
||||
return;
|
||||
}
|
||||
if ((message == null) || (message.getBody() == null)) {
|
||||
return;
|
||||
}
|
||||
if ((mXmppConnectionService.confirmMessages())
|
||||
&& ((packet.getId() != null))) {
|
||||
if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
|
||||
MessagePacket receipt = mXmppConnectionService
|
||||
.getMessageGenerator().received(account, packet,
|
||||
"urn:xmpp:chat-markers:0");
|
||||
mXmppConnectionService.sendMessagePacket(account, receipt);
|
||||
}
|
||||
if (packet.hasChild("request", "urn:xmpp:receipts")) {
|
||||
MessagePacket receipt = mXmppConnectionService
|
||||
.getMessageGenerator().received(account, packet,
|
||||
"urn:xmpp:receipts");
|
||||
mXmppConnectionService.sendMessagePacket(account, receipt);
|
||||
}
|
||||
}
|
||||
Conversation conversation = message.getConversation();
|
||||
conversation.add(message);
|
||||
if (packet.getType() != MessagePacket.TYPE_ERROR) {
|
||||
if (message.getEncryption() == Message.ENCRYPTION_NONE
|
||||
|| mXmppConnectionService.saveEncryptedMessages()) {
|
||||
mXmppConnectionService.databaseBackend.createMessage(message);
|
||||
}
|
||||
}
|
||||
if (message.bodyContainsDownloadable()) {
|
||||
this.mXmppConnectionService.getHttpConnectionManager()
|
||||
.createNewConnection(message);
|
||||
}
|
||||
notify = notify && !conversation.isMuted();
|
||||
if (notify) {
|
||||
mXmppConnectionService.getNotificationService().push(message);
|
||||
}
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
}
|
||||
|
||||
private void parseHeadline(MessagePacket packet, Account account) {
|
||||
if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
|
||||
Element event = packet.findChild("event",
|
||||
"http://jabber.org/protocol/pubsub#event");
|
||||
parseEvent(event, packet.getFrom(), account);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseNick(MessagePacket packet, Account account) {
|
||||
Element nick = packet.findChild("nick",
|
||||
"http://jabber.org/protocol/nick");
|
||||
if (nick != null) {
|
||||
if (packet.getFrom() != null) {
|
||||
Contact contact = account.getRoster().getContact(
|
||||
packet.getFrom());
|
||||
contact.setPresenceName(nick.getContent());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
package eu.siacs.conversations.parser;
|
||||
|
||||
import eu.siacs.conversations.crypto.PgpEngine;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Presences;
|
||||
import eu.siacs.conversations.generator.PresenceGenerator;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
|
||||
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
||||
|
||||
public class PresenceParser extends AbstractParser implements
|
||||
OnPresencePacketReceived {
|
||||
|
||||
public PresenceParser(XmppConnectionService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
public void parseConferencePresence(PresencePacket packet, Account account) {
|
||||
PgpEngine mPgpEngine = mXmppConnectionService.getPgpEngine();
|
||||
if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) {
|
||||
Conversation muc = mXmppConnectionService.find(account, packet
|
||||
.getAttribute("from").split("/", 2)[0]);
|
||||
if (muc != null) {
|
||||
boolean before = muc.getMucOptions().online();
|
||||
muc.getMucOptions().processPacket(packet, mPgpEngine);
|
||||
if (before != muc.getMucOptions().online()) {
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
}
|
||||
mXmppConnectionService.getAvatarService().clear(muc);
|
||||
}
|
||||
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
|
||||
Conversation muc = mXmppConnectionService.find(account, packet
|
||||
.getAttribute("from").split("/", 2)[0]);
|
||||
if (muc != null) {
|
||||
boolean before = muc.getMucOptions().online();
|
||||
muc.getMucOptions().processPacket(packet, mPgpEngine);
|
||||
if (before != muc.getMucOptions().online()) {
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
}
|
||||
mXmppConnectionService.getAvatarService().clear(muc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void parseContactPresence(PresencePacket packet, Account account) {
|
||||
PresenceGenerator mPresenceGenerator = mXmppConnectionService
|
||||
.getPresenceGenerator();
|
||||
if (packet.getFrom() == null) {
|
||||
return;
|
||||
}
|
||||
String[] fromParts = packet.getFrom().split("/", 2);
|
||||
String type = packet.getAttribute("type");
|
||||
if (fromParts[0].equals(account.getJid())) {
|
||||
if (fromParts.length == 2) {
|
||||
if (type == null) {
|
||||
account.updatePresence(fromParts[1],
|
||||
Presences.parseShow(packet.findChild("show")));
|
||||
} else if (type.equals("unavailable")) {
|
||||
account.removePresence(fromParts[1]);
|
||||
account.deactivateGracePeriod();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Contact contact = account.getRoster().getContact(packet.getFrom());
|
||||
if (type == null) {
|
||||
String presence;
|
||||
if (fromParts.length >= 2) {
|
||||
presence = fromParts[1];
|
||||
} else {
|
||||
presence = "";
|
||||
}
|
||||
int sizeBefore = contact.getPresences().size();
|
||||
contact.updatePresence(presence,
|
||||
Presences.parseShow(packet.findChild("show")));
|
||||
PgpEngine pgp = mXmppConnectionService.getPgpEngine();
|
||||
if (pgp != null) {
|
||||
Element x = packet.findChild("x", "jabber:x:signed");
|
||||
if (x != null) {
|
||||
Element status = packet.findChild("status");
|
||||
String msg;
|
||||
if (status != null) {
|
||||
msg = status.getContent();
|
||||
} else {
|
||||
msg = "";
|
||||
}
|
||||
contact.setPgpKeyId(pgp.fetchKeyId(account, msg,
|
||||
x.getContent()));
|
||||
}
|
||||
}
|
||||
boolean online = sizeBefore < contact.getPresences().size();
|
||||
updateLastseen(packet, account, true);
|
||||
mXmppConnectionService.onContactStatusChanged
|
||||
.onContactStatusChanged(contact, online);
|
||||
} else if (type.equals("unavailable")) {
|
||||
if (fromParts.length != 2) {
|
||||
contact.clearPresences();
|
||||
} else {
|
||||
contact.removePresence(fromParts[1]);
|
||||
}
|
||||
mXmppConnectionService.onContactStatusChanged
|
||||
.onContactStatusChanged(contact, false);
|
||||
} else if (type.equals("subscribe")) {
|
||||
if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) {
|
||||
mXmppConnectionService.sendPresencePacket(account,
|
||||
mPresenceGenerator.sendPresenceUpdatesTo(contact));
|
||||
} else {
|
||||
contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST);
|
||||
}
|
||||
}
|
||||
Element nick = packet.findChild("nick",
|
||||
"http://jabber.org/protocol/nick");
|
||||
if (nick != null) {
|
||||
contact.setPresenceName(nick.getContent());
|
||||
}
|
||||
}
|
||||
mXmppConnectionService.updateRosterUi();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPresencePacketReceived(Account account, PresencePacket packet) {
|
||||
if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) {
|
||||
this.parseConferencePresence(packet, account);
|
||||
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
|
||||
this.parseConferencePresence(packet, account);
|
||||
} else {
|
||||
this.parseContactPresence(packet, account);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,335 @@
|
||||
package eu.siacs.conversations.persistance;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.Roster;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteCantOpenDatabaseException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
public class DatabaseBackend extends SQLiteOpenHelper {
|
||||
|
||||
private static DatabaseBackend instance = null;
|
||||
|
||||
private static final String DATABASE_NAME = "history";
|
||||
private static final int DATABASE_VERSION = 8;
|
||||
|
||||
private static String CREATE_CONTATCS_STATEMENT = "create table "
|
||||
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
|
||||
+ Contact.SERVERNAME + " TEXT, " + Contact.SYSTEMNAME + " TEXT,"
|
||||
+ Contact.JID + " TEXT," + Contact.KEYS + " TEXT,"
|
||||
+ Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER,"
|
||||
+ Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, "
|
||||
+ "FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES "
|
||||
+ Account.TABLENAME + "(" + Account.UUID
|
||||
+ ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", "
|
||||
+ Contact.JID + ") ON CONFLICT REPLACE);";
|
||||
|
||||
private DatabaseBackend(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL("PRAGMA foreign_keys=ON;");
|
||||
db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID
|
||||
+ " TEXT PRIMARY KEY," + Account.USERNAME + " TEXT,"
|
||||
+ Account.SERVER + " TEXT," + Account.PASSWORD + " TEXT,"
|
||||
+ Account.ROSTERVERSION + " TEXT," + Account.OPTIONS
|
||||
+ " NUMBER, " + Account.AVATAR + " TEXT, " + Account.KEYS
|
||||
+ " TEXT)");
|
||||
db.execSQL("create table " + Conversation.TABLENAME + " ("
|
||||
+ Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
|
||||
+ " TEXT, " + Conversation.CONTACT + " TEXT, "
|
||||
+ Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACTJID
|
||||
+ " TEXT, " + Conversation.CREATED + " NUMBER, "
|
||||
+ Conversation.STATUS + " NUMBER, " + Conversation.MODE
|
||||
+ " NUMBER, " + Conversation.ATTRIBUTES + " TEXT, FOREIGN KEY("
|
||||
+ Conversation.ACCOUNT + ") REFERENCES " + Account.TABLENAME
|
||||
+ "(" + Account.UUID + ") ON DELETE CASCADE);");
|
||||
db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID
|
||||
+ " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, "
|
||||
+ Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART
|
||||
+ " TEXT, " + Message.TRUE_COUNTERPART + " TEXT,"
|
||||
+ Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, "
|
||||
+ Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
|
||||
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
|
||||
+ Message.CONVERSATION + ") REFERENCES "
|
||||
+ Conversation.TABLENAME + "(" + Conversation.UUID
|
||||
+ ") ON DELETE CASCADE);");
|
||||
|
||||
db.execSQL(CREATE_CONTATCS_STATEMENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion < 2 && newVersion >= 2) {
|
||||
db.execSQL("update " + Account.TABLENAME + " set "
|
||||
+ Account.OPTIONS + " = " + Account.OPTIONS + " | 8");
|
||||
}
|
||||
if (oldVersion < 3 && newVersion >= 3) {
|
||||
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
|
||||
+ Message.TYPE + " NUMBER");
|
||||
}
|
||||
if (oldVersion < 5 && newVersion >= 5) {
|
||||
db.execSQL("DROP TABLE " + Contact.TABLENAME);
|
||||
db.execSQL(CREATE_CONTATCS_STATEMENT);
|
||||
db.execSQL("UPDATE " + Account.TABLENAME + " SET "
|
||||
+ Account.ROSTERVERSION + " = NULL");
|
||||
}
|
||||
if (oldVersion < 6 && newVersion >= 6) {
|
||||
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
|
||||
+ Message.TRUE_COUNTERPART + " TEXT");
|
||||
}
|
||||
if (oldVersion < 7 && newVersion >= 7) {
|
||||
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
|
||||
+ Message.REMOTE_MSG_ID + " TEXT");
|
||||
db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
|
||||
+ Contact.AVATAR + " TEXT");
|
||||
db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN "
|
||||
+ Account.AVATAR + " TEXT");
|
||||
}
|
||||
if (oldVersion < 8 && newVersion >= 8) {
|
||||
db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN "
|
||||
+ Conversation.ATTRIBUTES + " TEXT");
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized DatabaseBackend getInstance(Context context) {
|
||||
if (instance == null) {
|
||||
instance = new DatabaseBackend(context);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void createConversation(Conversation conversation) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
db.insert(Conversation.TABLENAME, null, conversation.getContentValues());
|
||||
}
|
||||
|
||||
public void createMessage(Message message) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
db.insert(Message.TABLENAME, null, message.getContentValues());
|
||||
}
|
||||
|
||||
public void createAccount(Account account) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
db.insert(Account.TABLENAME, null, account.getContentValues());
|
||||
}
|
||||
|
||||
public void createContact(Contact contact) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
db.insert(Contact.TABLENAME, null, contact.getContentValues());
|
||||
}
|
||||
|
||||
public int getConversationCount() {
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
Cursor cursor = db.rawQuery("select count(uuid) as count from "
|
||||
+ Conversation.TABLENAME + " where " + Conversation.STATUS
|
||||
+ "=" + Conversation.STATUS_AVAILABLE, null);
|
||||
cursor.moveToFirst();
|
||||
return cursor.getInt(0);
|
||||
}
|
||||
|
||||
public CopyOnWriteArrayList<Conversation> getConversations(int status) {
|
||||
CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<Conversation>();
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
String[] selectionArgs = { Integer.toString(status) };
|
||||
Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME
|
||||
+ " where " + Conversation.STATUS + " = ? order by "
|
||||
+ Conversation.CREATED + " desc", selectionArgs);
|
||||
while (cursor.moveToNext()) {
|
||||
list.add(Conversation.fromCursor(cursor));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public ArrayList<Message> getMessages(Conversation conversations, int limit) {
|
||||
return getMessages(conversations, limit, -1);
|
||||
}
|
||||
|
||||
public ArrayList<Message> getMessages(Conversation conversation, int limit,
|
||||
long timestamp) {
|
||||
ArrayList<Message> list = new ArrayList<Message>();
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
Cursor cursor;
|
||||
if (timestamp == -1) {
|
||||
String[] selectionArgs = { conversation.getUuid() };
|
||||
cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
|
||||
+ "=?", selectionArgs, null, null, Message.TIME_SENT
|
||||
+ " DESC", String.valueOf(limit));
|
||||
} else {
|
||||
String[] selectionArgs = { conversation.getUuid(),
|
||||
Long.toString(timestamp) };
|
||||
cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
|
||||
+ "=? and " + Message.TIME_SENT + "<?", selectionArgs,
|
||||
null, null, Message.TIME_SENT + " DESC",
|
||||
String.valueOf(limit));
|
||||
}
|
||||
if (cursor.getCount() > 0) {
|
||||
cursor.moveToLast();
|
||||
do {
|
||||
Message message = Message.fromCursor(cursor);
|
||||
message.setConversation(conversation);
|
||||
list.add(message);
|
||||
} while (cursor.moveToPrevious());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public Conversation findConversation(Account account, String contactJid) {
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
String[] selectionArgs = { account.getUuid(), contactJid + "%" };
|
||||
Cursor cursor = db.query(Conversation.TABLENAME, null,
|
||||
Conversation.ACCOUNT + "=? AND " + Conversation.CONTACTJID
|
||||
+ " like ?", selectionArgs, null, null, null);
|
||||
if (cursor.getCount() == 0)
|
||||
return null;
|
||||
cursor.moveToFirst();
|
||||
return Conversation.fromCursor(cursor);
|
||||
}
|
||||
|
||||
public void updateConversation(Conversation conversation) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
String[] args = { conversation.getUuid() };
|
||||
db.update(Conversation.TABLENAME, conversation.getContentValues(),
|
||||
Conversation.UUID + "=?", args);
|
||||
}
|
||||
|
||||
public List<Account> getAccounts() {
|
||||
List<Account> list = new ArrayList<Account>();
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
Cursor cursor = db.query(Account.TABLENAME, null, null, null, null,
|
||||
null, null);
|
||||
while (cursor.moveToNext()) {
|
||||
list.add(Account.fromCursor(cursor));
|
||||
}
|
||||
cursor.close();
|
||||
return list;
|
||||
}
|
||||
|
||||
public void updateAccount(Account account) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
String[] args = { account.getUuid() };
|
||||
db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
|
||||
+ "=?", args);
|
||||
}
|
||||
|
||||
public void deleteAccount(Account account) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
String[] args = { account.getUuid() };
|
||||
db.delete(Account.TABLENAME, Account.UUID + "=?", args);
|
||||
}
|
||||
|
||||
public boolean hasEnabledAccounts() {
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
Cursor cursor = db.rawQuery("select count(" + Account.UUID + ") from "
|
||||
+ Account.TABLENAME + " where not options & (1 <<1)", null);
|
||||
try {
|
||||
cursor.moveToFirst();
|
||||
int count = cursor.getInt(0);
|
||||
cursor.close();
|
||||
return (count > 0);
|
||||
} catch (SQLiteCantOpenDatabaseException e) {
|
||||
return true; // better safe than sorry
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLiteDatabase getWritableDatabase() {
|
||||
SQLiteDatabase db = super.getWritableDatabase();
|
||||
db.execSQL("PRAGMA foreign_keys=ON;");
|
||||
return db;
|
||||
}
|
||||
|
||||
public void updateMessage(Message message) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
String[] args = { message.getUuid() };
|
||||
db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
|
||||
+ "=?", args);
|
||||
}
|
||||
|
||||
public void readRoster(Roster roster) {
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
Cursor cursor;
|
||||
String args[] = { roster.getAccount().getUuid() };
|
||||
cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?",
|
||||
args, null, null, null);
|
||||
while (cursor.moveToNext()) {
|
||||
roster.initContact(Contact.fromCursor(cursor));
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
public void writeRoster(Roster roster) {
|
||||
Account account = roster.getAccount();
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
for (Contact contact : roster.getContacts()) {
|
||||
if (contact.getOption(Contact.Options.IN_ROSTER)) {
|
||||
db.insert(Contact.TABLENAME, null, contact.getContentValues());
|
||||
} else {
|
||||
String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?";
|
||||
String[] whereArgs = { account.getUuid(), contact.getJid() };
|
||||
db.delete(Contact.TABLENAME, where, whereArgs);
|
||||
}
|
||||
}
|
||||
account.setRosterVersion(roster.getVersion());
|
||||
updateAccount(account);
|
||||
}
|
||||
|
||||
public void deleteMessage(Message message) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
String[] args = { message.getUuid() };
|
||||
db.delete(Message.TABLENAME, Message.UUID + "=?", args);
|
||||
}
|
||||
|
||||
public void deleteMessagesInConversation(Conversation conversation) {
|
||||
SQLiteDatabase db = this.getWritableDatabase();
|
||||
String[] args = { conversation.getUuid() };
|
||||
db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
|
||||
}
|
||||
|
||||
public Conversation findConversationByUuid(String conversationUuid) {
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
String[] selectionArgs = { conversationUuid };
|
||||
Cursor cursor = db.query(Conversation.TABLENAME, null,
|
||||
Conversation.UUID + "=?", selectionArgs, null, null, null);
|
||||
if (cursor.getCount() == 0) {
|
||||
return null;
|
||||
}
|
||||
cursor.moveToFirst();
|
||||
return Conversation.fromCursor(cursor);
|
||||
}
|
||||
|
||||
public Message findMessageByUuid(String messageUuid) {
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
String[] selectionArgs = { messageUuid };
|
||||
Cursor cursor = db.query(Message.TABLENAME, null, Message.UUID + "=?",
|
||||
selectionArgs, null, null, null);
|
||||
if (cursor.getCount() == 0) {
|
||||
return null;
|
||||
}
|
||||
cursor.moveToFirst();
|
||||
return Message.fromCursor(cursor);
|
||||
}
|
||||
|
||||
public Account findAccountByUuid(String accountUuid) {
|
||||
SQLiteDatabase db = this.getReadableDatabase();
|
||||
String[] selectionArgs = { accountUuid };
|
||||
Cursor cursor = db.query(Account.TABLENAME, null, Account.UUID + "=?",
|
||||
selectionArgs, null, null, null);
|
||||
if (cursor.getCount() == 0) {
|
||||
return null;
|
||||
}
|
||||
cursor.moveToFirst();
|
||||
return Account.fromCursor(cursor);
|
||||
}
|
||||
}
|
@ -0,0 +1,480 @@
|
||||
package eu.siacs.conversations.persistance;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Base64;
|
||||
import android.util.Base64OutputStream;
|
||||
import android.util.Log;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||
|
||||
public class FileBackend {
|
||||
|
||||
private static int IMAGE_SIZE = 1920;
|
||||
|
||||
private SimpleDateFormat imageDateFormat = new SimpleDateFormat(
|
||||
"yyyyMMdd_HHmmssSSS", Locale.US);
|
||||
|
||||
private XmppConnectionService mXmppConnectionService;
|
||||
|
||||
public FileBackend(XmppConnectionService service) {
|
||||
this.mXmppConnectionService = service;
|
||||
}
|
||||
|
||||
public DownloadableFile getFile(Message message) {
|
||||
return getFile(message, true);
|
||||
}
|
||||
|
||||
public DownloadableFile getFile(Message message, boolean decrypted) {
|
||||
StringBuilder filename = new StringBuilder();
|
||||
filename.append(getConversationsDirectory());
|
||||
filename.append(message.getUuid());
|
||||
if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) {
|
||||
filename.append(".webp");
|
||||
} else {
|
||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||
filename.append(".webp");
|
||||
} else {
|
||||
filename.append(".webp.pgp");
|
||||
}
|
||||
}
|
||||
return new DownloadableFile(filename.toString());
|
||||
}
|
||||
|
||||
public static String getConversationsDirectory() {
|
||||
return Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_PICTURES).getAbsolutePath()
|
||||
+ "/Conversations/";
|
||||
}
|
||||
|
||||
public Bitmap resize(Bitmap originalBitmap, int size) {
|
||||
int w = originalBitmap.getWidth();
|
||||
int h = originalBitmap.getHeight();
|
||||
if (Math.max(w, h) > size) {
|
||||
int scalledW;
|
||||
int scalledH;
|
||||
if (w <= h) {
|
||||
scalledW = (int) (w / ((double) h / size));
|
||||
scalledH = size;
|
||||
} else {
|
||||
scalledW = size;
|
||||
scalledH = (int) (h / ((double) w / size));
|
||||
}
|
||||
Bitmap scalledBitmap = Bitmap.createScaledBitmap(originalBitmap,
|
||||
scalledW, scalledH, true);
|
||||
return scalledBitmap;
|
||||
} else {
|
||||
return originalBitmap;
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap rotate(Bitmap bitmap, int degree) {
|
||||
int w = bitmap.getWidth();
|
||||
int h = bitmap.getHeight();
|
||||
Matrix mtx = new Matrix();
|
||||
mtx.postRotate(degree);
|
||||
return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
|
||||
}
|
||||
|
||||
public DownloadableFile copyImageToPrivateStorage(Message message, Uri image)
|
||||
throws ImageCopyException {
|
||||
return this.copyImageToPrivateStorage(message, image, 0);
|
||||
}
|
||||
|
||||
private DownloadableFile copyImageToPrivateStorage(Message message,
|
||||
Uri image, int sampleSize) throws ImageCopyException {
|
||||
try {
|
||||
InputStream is = mXmppConnectionService.getContentResolver()
|
||||
.openInputStream(image);
|
||||
DownloadableFile file = getFile(message);
|
||||
file.getParentFile().mkdirs();
|
||||
file.createNewFile();
|
||||
Bitmap originalBitmap;
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
int inSampleSize = (int) Math.pow(2, sampleSize);
|
||||
Log.d(Config.LOGTAG, "reading bitmap with sample size "
|
||||
+ inSampleSize);
|
||||
options.inSampleSize = inSampleSize;
|
||||
originalBitmap = BitmapFactory.decodeStream(is, null, options);
|
||||
is.close();
|
||||
if (originalBitmap == null) {
|
||||
throw new ImageCopyException(R.string.error_not_an_image_file);
|
||||
}
|
||||
Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
|
||||
originalBitmap = null;
|
||||
int rotation = getRotation(image);
|
||||
if (rotation > 0) {
|
||||
scalledBitmap = rotate(scalledBitmap, rotation);
|
||||
}
|
||||
OutputStream os = new FileOutputStream(file);
|
||||
boolean success = scalledBitmap.compress(
|
||||
Bitmap.CompressFormat.WEBP, 75, os);
|
||||
if (!success) {
|
||||
throw new ImageCopyException(R.string.error_compressing_image);
|
||||
}
|
||||
os.flush();
|
||||
os.close();
|
||||
long size = file.getSize();
|
||||
int width = scalledBitmap.getWidth();
|
||||
int height = scalledBitmap.getHeight();
|
||||
message.setBody(Long.toString(size) + ',' + width + ',' + height);
|
||||
return file;
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new ImageCopyException(R.string.error_file_not_found);
|
||||
} catch (IOException e) {
|
||||
throw new ImageCopyException(R.string.error_io_exception);
|
||||
} catch (SecurityException e) {
|
||||
throw new ImageCopyException(
|
||||
R.string.error_security_exception_during_image_copy);
|
||||
} catch (OutOfMemoryError e) {
|
||||
++sampleSize;
|
||||
if (sampleSize <= 3) {
|
||||
return copyImageToPrivateStorage(message, image, sampleSize);
|
||||
} else {
|
||||
throw new ImageCopyException(R.string.error_out_of_memory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getRotation(Uri image) {
|
||||
if ("content".equals(image.getScheme())) {
|
||||
try {
|
||||
Cursor cursor = mXmppConnectionService
|
||||
.getContentResolver()
|
||||
.query(image,
|
||||
new String[] { MediaStore.Images.ImageColumns.ORIENTATION },
|
||||
null, null, null);
|
||||
if (cursor.getCount() != 1) {
|
||||
return -1;
|
||||
}
|
||||
cursor.moveToFirst();
|
||||
return cursor.getInt(0);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
ExifInterface exif;
|
||||
try {
|
||||
exif = new ExifInterface(image.toString());
|
||||
if (exif.getAttribute(ExifInterface.TAG_ORIENTATION)
|
||||
.equalsIgnoreCase("6")) {
|
||||
return 90;
|
||||
} else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION)
|
||||
.equalsIgnoreCase("8")) {
|
||||
return 270;
|
||||
} else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION)
|
||||
.equalsIgnoreCase("3")) {
|
||||
return 180;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap getImageFromMessage(Message message) {
|
||||
return BitmapFactory.decodeFile(getFile(message).getAbsolutePath());
|
||||
}
|
||||
|
||||
public Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
|
||||
throws FileNotFoundException {
|
||||
Bitmap thumbnail = mXmppConnectionService.getBitmapCache().get(
|
||||
message.getUuid());
|
||||
if ((thumbnail == null) && (!cacheOnly)) {
|
||||
File file = getFile(message);
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inSampleSize = calcSampleSize(file, size);
|
||||
Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),
|
||||
options);
|
||||
if (fullsize == null) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
thumbnail = resize(fullsize, size);
|
||||
this.mXmppConnectionService.getBitmapCache().put(message.getUuid(),
|
||||
thumbnail);
|
||||
}
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
public void removeFiles(Conversation conversation) {
|
||||
String prefix = mXmppConnectionService.getFilesDir().getAbsolutePath();
|
||||
String path = prefix + "/" + conversation.getAccount().getJid() + "/"
|
||||
+ conversation.getContactJid();
|
||||
File file = new File(path);
|
||||
try {
|
||||
this.deleteFile(file);
|
||||
} catch (IOException e) {
|
||||
Log.d(Config.LOGTAG,
|
||||
"error deleting file: " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteFile(File f) throws IOException {
|
||||
if (f.isDirectory()) {
|
||||
for (File c : f.listFiles())
|
||||
deleteFile(c);
|
||||
}
|
||||
f.delete();
|
||||
}
|
||||
|
||||
public Uri getTakePhotoUri() {
|
||||
StringBuilder pathBuilder = new StringBuilder();
|
||||
pathBuilder.append(Environment
|
||||
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
|
||||
pathBuilder.append('/');
|
||||
pathBuilder.append("Camera");
|
||||
pathBuilder.append('/');
|
||||
pathBuilder.append("IMG_" + this.imageDateFormat.format(new Date())
|
||||
+ ".jpg");
|
||||
Uri uri = Uri.parse("file://" + pathBuilder.toString());
|
||||
File file = new File(uri.toString());
|
||||
file.getParentFile().mkdirs();
|
||||
return uri;
|
||||
}
|
||||
|
||||
public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) {
|
||||
try {
|
||||
Avatar avatar = new Avatar();
|
||||
Bitmap bm = cropCenterSquare(image, size);
|
||||
if (bm == null) {
|
||||
return null;
|
||||
}
|
||||
ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
|
||||
Base64OutputStream mBase64OutputSttream = new Base64OutputStream(
|
||||
mByteArrayOutputStream, Base64.DEFAULT);
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
DigestOutputStream mDigestOutputStream = new DigestOutputStream(
|
||||
mBase64OutputSttream, digest);
|
||||
if (!bm.compress(format, 75, mDigestOutputStream)) {
|
||||
return null;
|
||||
}
|
||||
mDigestOutputStream.flush();
|
||||
mDigestOutputStream.close();
|
||||
avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest());
|
||||
avatar.image = new String(mByteArrayOutputStream.toByteArray());
|
||||
return avatar;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAvatarCached(Avatar avatar) {
|
||||
File file = new File(getAvatarPath(avatar.getFilename()));
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
public boolean save(Avatar avatar) {
|
||||
if (isAvatarCached(avatar)) {
|
||||
return true;
|
||||
}
|
||||
String filename = getAvatarPath(avatar.getFilename());
|
||||
File file = new File(filename + ".tmp");
|
||||
file.getParentFile().mkdirs();
|
||||
try {
|
||||
file.createNewFile();
|
||||
FileOutputStream mFileOutputStream = new FileOutputStream(file);
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
digest.reset();
|
||||
DigestOutputStream mDigestOutputStream = new DigestOutputStream(
|
||||
mFileOutputStream, digest);
|
||||
mDigestOutputStream.write(avatar.getImageAsBytes());
|
||||
mDigestOutputStream.flush();
|
||||
mDigestOutputStream.close();
|
||||
avatar.size = file.length();
|
||||
String sha1sum = CryptoHelper.bytesToHex(digest.digest());
|
||||
if (sha1sum.equals(avatar.sha1sum)) {
|
||||
file.renameTo(new File(filename));
|
||||
return true;
|
||||
} else {
|
||||
Log.d(Config.LOGTAG, "sha1sum mismatch for " + avatar.owner);
|
||||
file.delete();
|
||||
return false;
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getAvatarPath(String avatar) {
|
||||
return mXmppConnectionService.getFilesDir().getAbsolutePath()
|
||||
+ "/avatars/" + avatar;
|
||||
}
|
||||
|
||||
public Uri getAvatarUri(String avatar) {
|
||||
return Uri.parse("file:" + getAvatarPath(avatar));
|
||||
}
|
||||
|
||||
public Bitmap cropCenterSquare(Uri image, int size) {
|
||||
try {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inSampleSize = calcSampleSize(image, size);
|
||||
InputStream is = mXmppConnectionService.getContentResolver()
|
||||
.openInputStream(image);
|
||||
Bitmap input = BitmapFactory.decodeStream(is, null, options);
|
||||
if (input == null) {
|
||||
return null;
|
||||
} else {
|
||||
int rotation = getRotation(image);
|
||||
if (rotation > 0) {
|
||||
input = rotate(input, rotation);
|
||||
}
|
||||
return cropCenterSquare(input, size);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap cropCenter(Uri image, int newHeight, int newWidth) {
|
||||
try {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inSampleSize = calcSampleSize(image,
|
||||
Math.max(newHeight, newWidth));
|
||||
InputStream is = mXmppConnectionService.getContentResolver()
|
||||
.openInputStream(image);
|
||||
Bitmap source = BitmapFactory.decodeStream(is, null, options);
|
||||
|
||||
int sourceWidth = source.getWidth();
|
||||
int sourceHeight = source.getHeight();
|
||||
float xScale = (float) newWidth / sourceWidth;
|
||||
float yScale = (float) newHeight / sourceHeight;
|
||||
float scale = Math.max(xScale, yScale);
|
||||
float scaledWidth = scale * sourceWidth;
|
||||
float scaledHeight = scale * sourceHeight;
|
||||
float left = (newWidth - scaledWidth) / 2;
|
||||
float top = (newHeight - scaledHeight) / 2;
|
||||
|
||||
RectF targetRect = new RectF(left, top, left + scaledWidth, top
|
||||
+ scaledHeight);
|
||||
Bitmap dest = Bitmap.createBitmap(newWidth, newHeight,
|
||||
source.getConfig());
|
||||
Canvas canvas = new Canvas(dest);
|
||||
canvas.drawBitmap(source, null, targetRect, null);
|
||||
|
||||
return dest;
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Bitmap cropCenterSquare(Bitmap input, int size) {
|
||||
int w = input.getWidth();
|
||||
int h = input.getHeight();
|
||||
|
||||
float scale = Math.max((float) size / h, (float) size / w);
|
||||
|
||||
float outWidth = scale * w;
|
||||
float outHeight = scale * h;
|
||||
float left = (size - outWidth) / 2;
|
||||
float top = (size - outHeight) / 2;
|
||||
RectF target = new RectF(left, top, left + outWidth, top + outHeight);
|
||||
|
||||
Bitmap output = Bitmap.createBitmap(size, size, input.getConfig());
|
||||
Canvas canvas = new Canvas(output);
|
||||
canvas.drawBitmap(input, null, target, null);
|
||||
return output;
|
||||
}
|
||||
|
||||
private int calcSampleSize(Uri image, int size)
|
||||
throws FileNotFoundException {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver()
|
||||
.openInputStream(image), null, options);
|
||||
return calcSampleSize(options, size);
|
||||
}
|
||||
|
||||
private int calcSampleSize(File image, int size) {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(image.getAbsolutePath(), options);
|
||||
return calcSampleSize(options, size);
|
||||
}
|
||||
|
||||
private int calcSampleSize(BitmapFactory.Options options, int size) {
|
||||
int height = options.outHeight;
|
||||
int width = options.outWidth;
|
||||
int inSampleSize = 1;
|
||||
|
||||
if (height > size || width > size) {
|
||||
int halfHeight = height / 2;
|
||||
int halfWidth = width / 2;
|
||||
|
||||
while ((halfHeight / inSampleSize) > size
|
||||
&& (halfWidth / inSampleSize) > size) {
|
||||
inSampleSize *= 2;
|
||||
}
|
||||
}
|
||||
return inSampleSize;
|
||||
}
|
||||
|
||||
public Uri getJingleFileUri(Message message) {
|
||||
File file = getFile(message);
|
||||
return Uri.parse("file://" + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
public class ImageCopyException extends Exception {
|
||||
private static final long serialVersionUID = -1010013599132881427L;
|
||||
private int resId;
|
||||
|
||||
public ImageCopyException(int resId) {
|
||||
this.resId = resId;
|
||||
}
|
||||
|
||||
public int getResId() {
|
||||
return resId;
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap getAvatar(String avatar, int size) {
|
||||
if (avatar == null) {
|
||||
return null;
|
||||
}
|
||||
Bitmap bm = cropCenter(getAvatarUri(avatar), size, size);
|
||||
if (bm == null) {
|
||||
return null;
|
||||
}
|
||||
return bm;
|
||||
}
|
||||
|
||||
public boolean isFileAvailable(Message message) {
|
||||
return getFile(message).exists();
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package eu.siacs.conversations.persistance;
|
||||
|
||||
public interface OnPhoneContactsMerged {
|
||||
public void phoneContactsMerged();
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package eu.siacs.conversations.services;
|
||||
|
||||
public class AbstractConnectionManager {
|
||||
protected XmppConnectionService mXmppConnectionService;
|
||||
|
||||
public AbstractConnectionManager(XmppConnectionService service) {
|
||||
this.mXmppConnectionService = service;
|
||||
}
|
||||
|
||||
public XmppConnectionService getXmppConnectionService() {
|
||||
return this.mXmppConnectionService;
|
||||
}
|
||||
|
||||
public long getAutoAcceptFileSize() {
|
||||
String config = this.mXmppConnectionService.getPreferences().getString(
|
||||
"auto_accept_file_size", "524288");
|
||||
try {
|
||||
return Long.parseLong(config);
|
||||
} catch (NumberFormatException e) {
|
||||
return 524288;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,298 @@
|
||||
package eu.siacs.conversations.services;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Bookmark;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.ListItem;
|
||||
import eu.siacs.conversations.entities.MucOptions;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
public class AvatarService {
|
||||
|
||||
private static final int FG_COLOR = 0xFFFAFAFA;
|
||||
private static final int TRANSPARENT = 0x00000000;
|
||||
|
||||
private static final String PREFIX_CONTACT = "contact";
|
||||
private static final String PREFIX_CONVERSATION = "conversation";
|
||||
private static final String PREFIX_ACCOUNT = "account";
|
||||
private static final String PREFIX_GENERIC = "generic";
|
||||
|
||||
private ArrayList<Integer> sizes = new ArrayList<Integer>();
|
||||
|
||||
protected XmppConnectionService mXmppConnectionService = null;
|
||||
|
||||
public AvatarService(XmppConnectionService service) {
|
||||
this.mXmppConnectionService = service;
|
||||
}
|
||||
|
||||
public Bitmap get(Contact contact, int size) {
|
||||
final String KEY = key(contact, size);
|
||||
Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY);
|
||||
if (avatar != null) {
|
||||
return avatar;
|
||||
}
|
||||
Log.d(Config.LOGTAG, "no cache hit for " + KEY);
|
||||
avatar = mXmppConnectionService.getFileBackend().getAvatar(
|
||||
contact.getAvatar(), size);
|
||||
if (avatar == null) {
|
||||
if (contact.getProfilePhoto() != null) {
|
||||
avatar = mXmppConnectionService.getFileBackend()
|
||||
.cropCenterSquare(Uri.parse(contact.getProfilePhoto()),
|
||||
size);
|
||||
if (avatar == null) {
|
||||
avatar = get(contact.getDisplayName(), size);
|
||||
}
|
||||
} else {
|
||||
avatar = get(contact.getDisplayName(), size);
|
||||
}
|
||||
}
|
||||
this.mXmppConnectionService.getBitmapCache().put(KEY, avatar);
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void clear(Contact contact) {
|
||||
for (Integer size : sizes) {
|
||||
this.mXmppConnectionService.getBitmapCache().remove(
|
||||
key(contact, size));
|
||||
}
|
||||
}
|
||||
|
||||
private String key(Contact contact, int size) {
|
||||
synchronized (this.sizes) {
|
||||
if (!this.sizes.contains(size)) {
|
||||
this.sizes.add(size);
|
||||
}
|
||||
}
|
||||
return PREFIX_CONTACT + "_" + contact.getAccount().getJid() + "_"
|
||||
+ contact.getJid() + "_" + String.valueOf(size);
|
||||
}
|
||||
|
||||
public Bitmap get(ListItem item, int size) {
|
||||
if (item instanceof Contact) {
|
||||
return get((Contact) item, size);
|
||||
} else if (item instanceof Bookmark) {
|
||||
Bookmark bookmark = (Bookmark) item;
|
||||
if (bookmark.getConversation() != null) {
|
||||
return get(bookmark.getConversation(), size);
|
||||
} else {
|
||||
return get(bookmark.getDisplayName(), size);
|
||||
}
|
||||
} else {
|
||||
return get(item.getDisplayName(), size);
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap get(Conversation conversation, int size) {
|
||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||
return get(conversation.getContact(), size);
|
||||
} else {
|
||||
return get(conversation.getMucOptions(), size);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear(Conversation conversation) {
|
||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||
clear(conversation.getContact());
|
||||
} else {
|
||||
clear(conversation.getMucOptions());
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap get(MucOptions mucOptions, int size) {
|
||||
final String KEY = key(mucOptions, size);
|
||||
Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY);
|
||||
if (bitmap != null) {
|
||||
return bitmap;
|
||||
}
|
||||
Log.d(Config.LOGTAG, "no cache hit for " + KEY);
|
||||
List<MucOptions.User> users = mucOptions.getUsers();
|
||||
int count = users.size();
|
||||
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
bitmap.eraseColor(TRANSPARENT);
|
||||
|
||||
if (count == 0) {
|
||||
String name = mucOptions.getConversation().getName();
|
||||
String letter = name.substring(0, 1);
|
||||
int color = this.getColorForName(name);
|
||||
drawTile(canvas, letter, color, 0, 0, size, size);
|
||||
} else if (count == 1) {
|
||||
drawTile(canvas, users.get(0), 0, 0, size, size);
|
||||
} else if (count == 2) {
|
||||
drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size);
|
||||
drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size);
|
||||
} else if (count == 3) {
|
||||
drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size);
|
||||
drawTile(canvas, users.get(1), size / 2 + 1, 0, size, size / 2 - 1);
|
||||
drawTile(canvas, users.get(2), size / 2 + 1, size / 2 + 1, size,
|
||||
size);
|
||||
} else if (count == 4) {
|
||||
drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1);
|
||||
drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size);
|
||||
drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1);
|
||||
drawTile(canvas, users.get(3), size / 2 + 1, size / 2 + 1, size,
|
||||
size);
|
||||
} else {
|
||||
drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size / 2 - 1);
|
||||
drawTile(canvas, users.get(1), 0, size / 2 + 1, size / 2 - 1, size);
|
||||
drawTile(canvas, users.get(2), size / 2 + 1, 0, size, size / 2 - 1);
|
||||
drawTile(canvas, "\u2026", 0xFF202020, size / 2 + 1, size / 2 + 1,
|
||||
size, size);
|
||||
}
|
||||
this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public void clear(MucOptions options) {
|
||||
for (Integer size : sizes) {
|
||||
this.mXmppConnectionService.getBitmapCache().remove(
|
||||
key(options, size));
|
||||
}
|
||||
}
|
||||
|
||||
private String key(MucOptions options, int size) {
|
||||
synchronized (this.sizes) {
|
||||
if (!this.sizes.contains(size)) {
|
||||
this.sizes.add(size);
|
||||
}
|
||||
}
|
||||
return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid()
|
||||
+ "_" + String.valueOf(size);
|
||||
}
|
||||
|
||||
public Bitmap get(Account account, int size) {
|
||||
final String KEY = key(account, size);
|
||||
Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY);
|
||||
if (avatar != null) {
|
||||
return avatar;
|
||||
}
|
||||
Log.d(Config.LOGTAG, "no cache hit for " + KEY);
|
||||
avatar = mXmppConnectionService.getFileBackend().getAvatar(
|
||||
account.getAvatar(), size);
|
||||
if (avatar == null) {
|
||||
avatar = get(account.getJid(), size);
|
||||
}
|
||||
mXmppConnectionService.getBitmapCache().put(KEY, avatar);
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void clear(Account account) {
|
||||
for (Integer size : sizes) {
|
||||
this.mXmppConnectionService.getBitmapCache().remove(
|
||||
key(account, size));
|
||||
}
|
||||
}
|
||||
|
||||
private String key(Account account, int size) {
|
||||
synchronized (this.sizes) {
|
||||
if (!this.sizes.contains(size)) {
|
||||
this.sizes.add(size);
|
||||
}
|
||||
}
|
||||
return PREFIX_ACCOUNT + "_" + account.getUuid() + "_"
|
||||
+ String.valueOf(size);
|
||||
}
|
||||
|
||||
public Bitmap get(String name, int size) {
|
||||
final String KEY = key(name, size);
|
||||
Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY);
|
||||
if (bitmap != null) {
|
||||
return bitmap;
|
||||
}
|
||||
Log.d(Config.LOGTAG, "no cache hit for " + KEY);
|
||||
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
String letter = name.substring(0, 1);
|
||||
int color = this.getColorForName(name);
|
||||
drawTile(canvas, letter, color, 0, 0, size, size);
|
||||
mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private String key(String name, int size) {
|
||||
synchronized (this.sizes) {
|
||||
if (!this.sizes.contains(size)) {
|
||||
this.sizes.add(size);
|
||||
}
|
||||
}
|
||||
return PREFIX_GENERIC + "_" + name + "_" + String.valueOf(size);
|
||||
}
|
||||
|
||||
private void drawTile(Canvas canvas, String letter, int tileColor,
|
||||
int left, int top, int right, int bottom) {
|
||||
letter = letter.toUpperCase(Locale.getDefault());
|
||||
Paint tilePaint = new Paint(), textPaint = new Paint();
|
||||
tilePaint.setColor(tileColor);
|
||||
textPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
|
||||
textPaint.setColor(FG_COLOR);
|
||||
textPaint.setTypeface(Typeface.create("sans-serif-light",
|
||||
Typeface.NORMAL));
|
||||
textPaint.setTextSize((float) ((right - left) * 0.8));
|
||||
Rect rect = new Rect();
|
||||
|
||||
canvas.drawRect(new Rect(left, top, right, bottom), tilePaint);
|
||||
textPaint.getTextBounds(letter, 0, 1, rect);
|
||||
float width = textPaint.measureText(letter);
|
||||
canvas.drawText(letter, (right + left) / 2 - width / 2, (top + bottom)
|
||||
/ 2 + rect.height() / 2, textPaint);
|
||||
}
|
||||
|
||||
private void drawTile(Canvas canvas, MucOptions.User user, int left,
|
||||
int top, int right, int bottom) {
|
||||
Contact contact = user.getContact();
|
||||
if (contact != null) {
|
||||
Uri uri = null;
|
||||
if (contact.getAvatar() != null) {
|
||||
uri = mXmppConnectionService.getFileBackend().getAvatarUri(
|
||||
contact.getAvatar());
|
||||
} else if (contact.getProfilePhoto() != null) {
|
||||
uri = Uri.parse(contact.getProfilePhoto());
|
||||
}
|
||||
if (uri != null) {
|
||||
Bitmap bitmap = mXmppConnectionService.getFileBackend()
|
||||
.cropCenter(uri, bottom - top, right - left);
|
||||
if (bitmap != null) {
|
||||
drawTile(canvas, bitmap, left, top, right, bottom);
|
||||
} else {
|
||||
String letter = user.getName().substring(0, 1);
|
||||
int color = this.getColorForName(user.getName());
|
||||
drawTile(canvas, letter, color, left, top, right, bottom);
|
||||
}
|
||||
} else {
|
||||
String letter = user.getName().substring(0, 1);
|
||||
int color = this.getColorForName(user.getName());
|
||||
drawTile(canvas, letter, color, left, top, right, bottom);
|
||||
}
|
||||
} else {
|
||||
String letter = user.getName().substring(0, 1);
|
||||
int color = this.getColorForName(user.getName());
|
||||
drawTile(canvas, letter, color, left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop,
|
||||
int dstright, int dstbottom) {
|
||||
Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom);
|
||||
canvas.drawBitmap(bm, null, dst, null);
|
||||
}
|
||||
|
||||
private int getColorForName(String name) {
|
||||
int holoColors[] = { 0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5,
|
||||
0xFF5677fc, 0xFF03a9f4, 0xFF00bcd4, 0xFF009688, 0xFFff5722,
|
||||
0xFF795548, 0xFF607d8b };
|
||||
return holoColors[(int) ((name.hashCode() & 0xffffffffl) % holoColors.length)];
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package eu.siacs.conversations.services;
|
||||
|
||||
import eu.siacs.conversations.persistance.DatabaseBackend;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public class EventReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Intent mIntentForService = new Intent(context,
|
||||
XmppConnectionService.class);
|
||||
if (intent.getAction() != null) {
|
||||
mIntentForService.setAction(intent.getAction());
|
||||
} else {
|
||||
mIntentForService.setAction("other");
|
||||
}
|
||||
if (intent.getAction().equals("ui")
|
||||
|| DatabaseBackend.getInstance(context).hasEnabledAccounts()) {
|
||||
context.startService(mIntentForService);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,237 @@
|
||||
package eu.siacs.conversations.services;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.PowerManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.text.Html;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.ui.ConversationActivity;
|
||||
|
||||
public class NotificationService {
|
||||
|
||||
private XmppConnectionService mXmppConnectionService;
|
||||
|
||||
private LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<String, ArrayList<Message>>();
|
||||
|
||||
public int NOTIFICATION_ID = 0x2342;
|
||||
private Conversation mOpenConversation;
|
||||
private boolean mIsInForeground;
|
||||
|
||||
public NotificationService(XmppConnectionService service) {
|
||||
this.mXmppConnectionService = service;
|
||||
}
|
||||
|
||||
public void push(Message message) {
|
||||
PowerManager pm = (PowerManager) mXmppConnectionService
|
||||
.getSystemService(Context.POWER_SERVICE);
|
||||
boolean isScreenOn = pm.isScreenOn();
|
||||
|
||||
if (this.mIsInForeground && isScreenOn
|
||||
&& this.mOpenConversation == message.getConversation()) {
|
||||
return;
|
||||
}
|
||||
synchronized (notifications) {
|
||||
String conversationUuid = message.getConversationUuid();
|
||||
if (notifications.containsKey(conversationUuid)) {
|
||||
notifications.get(conversationUuid).add(message);
|
||||
} else {
|
||||
ArrayList<Message> mList = new ArrayList<Message>();
|
||||
mList.add(message);
|
||||
notifications.put(conversationUuid, mList);
|
||||
}
|
||||
Account account = message.getConversation().getAccount();
|
||||
updateNotification((!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn)
|
||||
&& !account.inGracePeriod());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
synchronized (notifications) {
|
||||
notifications.clear();
|
||||
updateNotification(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear(Conversation conversation) {
|
||||
synchronized (notifications) {
|
||||
notifications.remove(conversation.getUuid());
|
||||
updateNotification(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNotification(boolean notify) {
|
||||
NotificationManager notificationManager = (NotificationManager) mXmppConnectionService
|
||||
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
SharedPreferences preferences = mXmppConnectionService.getPreferences();
|
||||
|
||||
String ringtone = preferences.getString("notification_ringtone", null);
|
||||
boolean vibrate = preferences.getBoolean("vibrate_on_notification",
|
||||
true);
|
||||
|
||||
if (notifications.size() == 0) {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
} else {
|
||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
|
||||
mXmppConnectionService);
|
||||
mBuilder.setSmallIcon(R.drawable.ic_notification);
|
||||
if (notifications.size() == 1) {
|
||||
ArrayList<Message> messages = notifications.values().iterator()
|
||||
.next();
|
||||
if (messages.size() >= 1) {
|
||||
Conversation conversation = messages.get(0)
|
||||
.getConversation();
|
||||
mBuilder.setLargeIcon(mXmppConnectionService
|
||||
.getAvatarService().get(conversation, getPixel(64)));
|
||||
mBuilder.setContentTitle(conversation.getName());
|
||||
StringBuilder text = new StringBuilder();
|
||||
for (int i = 0; i < messages.size(); ++i) {
|
||||
text.append(messages.get(i).getReadableBody(
|
||||
mXmppConnectionService));
|
||||
if (i != messages.size() - 1) {
|
||||
text.append("\n");
|
||||
}
|
||||
}
|
||||
mBuilder.setStyle(new NotificationCompat.BigTextStyle()
|
||||
.bigText(text.toString()));
|
||||
mBuilder.setContentText(messages.get(0).getReadableBody(
|
||||
mXmppConnectionService));
|
||||
if (notify) {
|
||||
mBuilder.setTicker(messages.get(messages.size() - 1)
|
||||
.getReadableBody(mXmppConnectionService));
|
||||
}
|
||||
mBuilder.setContentIntent(createContentIntent(conversation
|
||||
.getUuid()));
|
||||
} else {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
|
||||
style.setBigContentTitle(notifications.size()
|
||||
+ " "
|
||||
+ mXmppConnectionService
|
||||
.getString(R.string.unread_conversations));
|
||||
StringBuilder names = new StringBuilder();
|
||||
Conversation conversation = null;
|
||||
for (ArrayList<Message> messages : notifications.values()) {
|
||||
if (messages.size() > 0) {
|
||||
conversation = messages.get(0).getConversation();
|
||||
String name = conversation.getName();
|
||||
style.addLine(Html.fromHtml("<b>"
|
||||
+ name
|
||||
+ "</b> "
|
||||
+ messages.get(0).getReadableBody(
|
||||
mXmppConnectionService)));
|
||||
names.append(name);
|
||||
names.append(", ");
|
||||
}
|
||||
}
|
||||
if (names.length() >= 2) {
|
||||
names.delete(names.length() - 2, names.length());
|
||||
}
|
||||
mBuilder.setContentTitle(notifications.size()
|
||||
+ " "
|
||||
+ mXmppConnectionService
|
||||
.getString(R.string.unread_conversations));
|
||||
mBuilder.setContentText(names.toString());
|
||||
mBuilder.setStyle(style);
|
||||
if (conversation != null) {
|
||||
mBuilder.setContentIntent(createContentIntent(conversation
|
||||
.getUuid()));
|
||||
}
|
||||
}
|
||||
if (notify) {
|
||||
if (vibrate) {
|
||||
int dat = 70;
|
||||
long[] pattern = { 0, 3 * dat, dat, dat };
|
||||
mBuilder.setVibrate(pattern);
|
||||
}
|
||||
if (ringtone != null) {
|
||||
mBuilder.setSound(Uri.parse(ringtone));
|
||||
}
|
||||
}
|
||||
mBuilder.setDeleteIntent(createDeleteIntent());
|
||||
mBuilder.setLights(0xffffffff, 2000, 4000);
|
||||
Notification notification = mBuilder.build();
|
||||
notificationManager.notify(NOTIFICATION_ID, notification);
|
||||
}
|
||||
}
|
||||
|
||||
private PendingIntent createContentIntent(String conversationUuid) {
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder
|
||||
.create(mXmppConnectionService);
|
||||
stackBuilder.addParentStack(ConversationActivity.class);
|
||||
|
||||
Intent viewConversationIntent = new Intent(mXmppConnectionService,
|
||||
ConversationActivity.class);
|
||||
viewConversationIntent.setAction(Intent.ACTION_VIEW);
|
||||
viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
|
||||
conversationUuid);
|
||||
viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION);
|
||||
|
||||
stackBuilder.addNextIntent(viewConversationIntent);
|
||||
|
||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
return resultPendingIntent;
|
||||
}
|
||||
|
||||
private PendingIntent createDeleteIntent() {
|
||||
Intent intent = new Intent(mXmppConnectionService,
|
||||
XmppConnectionService.class);
|
||||
intent.setAction("clear_notification");
|
||||
return PendingIntent.getService(mXmppConnectionService, 0, intent, 0);
|
||||
}
|
||||
|
||||
public static boolean wasHighlightedOrPrivate(Message message) {
|
||||
String nick = message.getConversation().getMucOptions().getActualNick();
|
||||
Pattern highlight = generateNickHighlightPattern(nick);
|
||||
if (message.getBody() == null || nick == null) {
|
||||
return false;
|
||||
}
|
||||
Matcher m = highlight.matcher(message.getBody());
|
||||
return (m.find() || message.getType() == Message.TYPE_PRIVATE);
|
||||
}
|
||||
|
||||
private static Pattern generateNickHighlightPattern(String nick) {
|
||||
// We expect a word boundary, i.e. space or start of string, followed by
|
||||
// the
|
||||
// nick (matched in case-insensitive manner), followed by optional
|
||||
// punctuation (for example "bob: i disagree" or "how are you alice?"),
|
||||
// followed by another word boundary.
|
||||
return Pattern.compile("\\b" + nick + "\\p{Punct}?\\b",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
|
||||
}
|
||||
|
||||
public void setOpenConversation(Conversation conversation) {
|
||||
this.mOpenConversation = conversation;
|
||||
}
|
||||
|
||||
public void setIsInForeground(boolean foreground) {
|
||||
this.mIsInForeground = foreground;
|
||||
}
|
||||
|
||||
private int getPixel(int dp) {
|
||||
DisplayMetrics metrics = mXmppConnectionService.getResources()
|
||||
.getDisplayMetrics();
|
||||
return ((int) (dp * metrics.density));
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,145 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.ListItem;
|
||||
import eu.siacs.conversations.ui.adapter.ListItemAdapter;
|
||||
|
||||
public class ChooseContactActivity extends XmppActivity {
|
||||
|
||||
private ListView mListView;
|
||||
private ArrayList<ListItem> contacts = new ArrayList<ListItem>();
|
||||
private ArrayAdapter<ListItem> mContactsAdapter;
|
||||
|
||||
private EditText mSearchEditText;
|
||||
|
||||
private TextWatcher mSearchTextWatcher = new TextWatcher() {
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
filterContacts(editable.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||
int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before,
|
||||
int count) {
|
||||
}
|
||||
};
|
||||
|
||||
private MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
||||
mSearchEditText.post(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mSearchEditText.requestFocus();
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mSearchEditText,
|
||||
InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(),
|
||||
InputMethodManager.HIDE_IMPLICIT_ONLY);
|
||||
mSearchEditText.setText("");
|
||||
filterContacts(null);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_choose_contact);
|
||||
mListView = (ListView) findViewById(R.id.choose_contact_list);
|
||||
mListView.setFastScrollEnabled(true);
|
||||
mContactsAdapter = new ListItemAdapter(this, contacts);
|
||||
mListView.setAdapter(mContactsAdapter);
|
||||
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> arg0, View arg1,
|
||||
int position, long arg3) {
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(),
|
||||
InputMethodManager.HIDE_IMPLICIT_ONLY);
|
||||
Intent request = getIntent();
|
||||
Intent data = new Intent();
|
||||
ListItem mListItem = contacts.get(position);
|
||||
data.putExtra("contact", mListItem.getJid());
|
||||
String account = request.getStringExtra("account");
|
||||
if (account == null && mListItem instanceof Contact) {
|
||||
account = ((Contact) mListItem).getAccount().getJid();
|
||||
}
|
||||
data.putExtra("account", account);
|
||||
data.putExtra("conversation",
|
||||
request.getStringExtra("conversation"));
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.choose_contact, menu);
|
||||
MenuItem menuSearchView = (MenuItem) menu.findItem(R.id.action_search);
|
||||
View mSearchView = menuSearchView.getActionView();
|
||||
mSearchEditText = (EditText) mSearchView
|
||||
.findViewById(R.id.search_field);
|
||||
mSearchEditText.addTextChangedListener(mSearchTextWatcher);
|
||||
menuSearchView.setOnActionExpandListener(mOnActionExpandListener);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onBackendConnected() {
|
||||
filterContacts(null);
|
||||
}
|
||||
|
||||
protected void filterContacts(String needle) {
|
||||
this.contacts.clear();
|
||||
for (Account account : xmppConnectionService.getAccounts()) {
|
||||
if (account.getStatus() != Account.STATUS_DISABLED) {
|
||||
for (Contact contact : account.getRoster().getContacts()) {
|
||||
if (contact.showInRoster() && contact.match(needle)) {
|
||||
this.contacts.add(contact);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.sort(this.contacts);
|
||||
mContactsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,280 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.crypto.PgpEngine;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.MucOptions;
|
||||
import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
|
||||
import eu.siacs.conversations.entities.MucOptions.User;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
|
||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class ConferenceDetailsActivity extends XmppActivity {
|
||||
public static final String ACTION_VIEW_MUC = "view_muc";
|
||||
private Conversation conversation;
|
||||
private TextView mYourNick;
|
||||
private ImageView mYourPhoto;
|
||||
private ImageButton mEditNickButton;
|
||||
private TextView mRoleAffiliaton;
|
||||
private TextView mFullJid;
|
||||
private TextView mAccountJid;
|
||||
private LinearLayout membersView;
|
||||
private LinearLayout mMoreDetails;
|
||||
private Button mInviteButton;
|
||||
private String uuid = null;
|
||||
|
||||
private OnClickListener inviteListener = new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
inviteToConversation(conversation);
|
||||
}
|
||||
};
|
||||
|
||||
private List<User> users = new ArrayList<MucOptions.User>();
|
||||
private OnConversationUpdate onConvChanged = new OnConversationUpdate() {
|
||||
|
||||
@Override
|
||||
public void onConversationUpdate() {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
populateView();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_muc_details);
|
||||
mYourNick = (TextView) findViewById(R.id.muc_your_nick);
|
||||
mYourPhoto = (ImageView) findViewById(R.id.your_photo);
|
||||
mEditNickButton = (ImageButton) findViewById(R.id.edit_nick_button);
|
||||
mFullJid = (TextView) findViewById(R.id.muc_jabberid);
|
||||
membersView = (LinearLayout) findViewById(R.id.muc_members);
|
||||
mAccountJid = (TextView) findViewById(R.id.details_account);
|
||||
mMoreDetails = (LinearLayout) findViewById(R.id.muc_more_details);
|
||||
mMoreDetails.setVisibility(View.GONE);
|
||||
mInviteButton = (Button) findViewById(R.id.invite);
|
||||
mInviteButton.setOnClickListener(inviteListener);
|
||||
getActionBar().setHomeButtonEnabled(true);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
mEditNickButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
quickEdit(conversation.getMucOptions().getActualNick(),
|
||||
new OnValueEdited() {
|
||||
|
||||
@Override
|
||||
public void onValueEdited(String value) {
|
||||
xmppConnectionService.renameInMuc(conversation,
|
||||
value);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem menuItem) {
|
||||
switch (menuItem.getItemId()) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
break;
|
||||
case R.id.action_edit_subject:
|
||||
if (conversation != null) {
|
||||
quickEdit(conversation.getName(), new OnValueEdited() {
|
||||
|
||||
@Override
|
||||
public void onValueEdited(String value) {
|
||||
MessagePacket packet = xmppConnectionService
|
||||
.getMessageGenerator().conferenceSubject(
|
||||
conversation, value);
|
||||
xmppConnectionService.sendMessagePacket(
|
||||
conversation.getAccount(), packet);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
return super.onOptionsItemSelected(menuItem);
|
||||
}
|
||||
|
||||
public String getReadableRole(int role) {
|
||||
switch (role) {
|
||||
case User.ROLE_MODERATOR:
|
||||
return getString(R.string.moderator);
|
||||
case User.ROLE_PARTICIPANT:
|
||||
return getString(R.string.participant);
|
||||
case User.ROLE_VISITOR:
|
||||
return getString(R.string.visitor);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.muc_details, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onBackendConnected() {
|
||||
registerListener();
|
||||
if (getIntent().getAction().equals(ACTION_VIEW_MUC)) {
|
||||
this.uuid = getIntent().getExtras().getString("uuid");
|
||||
}
|
||||
if (uuid != null) {
|
||||
this.conversation = xmppConnectionService
|
||||
.findConversationByUuid(uuid);
|
||||
if (this.conversation != null) {
|
||||
populateView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
if (xmppConnectionServiceBound) {
|
||||
xmppConnectionService.removeOnConversationListChangedListener();
|
||||
}
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
protected void registerListener() {
|
||||
xmppConnectionService
|
||||
.setOnConversationListChangedListener(this.onConvChanged);
|
||||
xmppConnectionService.setOnRenameListener(new OnRenameListener() {
|
||||
|
||||
@Override
|
||||
public void onRename(final boolean success) {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
populateView();
|
||||
if (success) {
|
||||
Toast.makeText(
|
||||
ConferenceDetailsActivity.this,
|
||||
getString(R.string.your_nick_has_been_changed),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(ConferenceDetailsActivity.this,
|
||||
getString(R.string.nick_in_use),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void populateView() {
|
||||
mAccountJid.setText(getString(R.string.using_account, conversation
|
||||
.getAccount().getJid()));
|
||||
mYourPhoto.setImageBitmap(avatarService().get(
|
||||
conversation.getAccount(), getPixel(48)));
|
||||
setTitle(conversation.getName());
|
||||
mFullJid.setText(conversation.getContactJid().split("/", 2)[0]);
|
||||
mYourNick.setText(conversation.getMucOptions().getActualNick());
|
||||
mRoleAffiliaton = (TextView) findViewById(R.id.muc_role);
|
||||
if (conversation.getMucOptions().online()) {
|
||||
mMoreDetails.setVisibility(View.VISIBLE);
|
||||
User self = conversation.getMucOptions().getSelf();
|
||||
switch (self.getAffiliation()) {
|
||||
case User.AFFILIATION_ADMIN:
|
||||
mRoleAffiliaton.setText(getReadableRole(self.getRole()) + " ("
|
||||
+ getString(R.string.admin) + ")");
|
||||
break;
|
||||
case User.AFFILIATION_OWNER:
|
||||
mRoleAffiliaton.setText(getReadableRole(self.getRole()) + " ("
|
||||
+ getString(R.string.owner) + ")");
|
||||
break;
|
||||
default:
|
||||
mRoleAffiliaton.setText(getReadableRole(self.getRole()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.users.clear();
|
||||
this.users.addAll(conversation.getMucOptions().getUsers());
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
membersView.removeAllViews();
|
||||
for (final User user : conversation.getMucOptions().getUsers()) {
|
||||
View view = (View) inflater.inflate(R.layout.contact, membersView,
|
||||
false);
|
||||
TextView name = (TextView) view
|
||||
.findViewById(R.id.contact_display_name);
|
||||
TextView key = (TextView) view.findViewById(R.id.key);
|
||||
TextView role = (TextView) view.findViewById(R.id.contact_jid);
|
||||
if (user.getPgpKeyId() != 0) {
|
||||
key.setVisibility(View.VISIBLE);
|
||||
key.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
viewPgpKey(user);
|
||||
}
|
||||
});
|
||||
key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId()));
|
||||
}
|
||||
Bitmap bm;
|
||||
Contact contact = user.getContact();
|
||||
if (contact != null) {
|
||||
bm = avatarService().get(contact, getPixel(48));
|
||||
name.setText(contact.getDisplayName());
|
||||
role.setText(user.getName() + " \u2022 "
|
||||
+ getReadableRole(user.getRole()));
|
||||
} else {
|
||||
bm = avatarService().get(user.getName(), getPixel(48));
|
||||
name.setText(user.getName());
|
||||
role.setText(getReadableRole(user.getRole()));
|
||||
}
|
||||
ImageView iv = (ImageView) view.findViewById(R.id.contact_photo);
|
||||
iv.setImageBitmap(bm);
|
||||
membersView.addView(view);
|
||||
}
|
||||
}
|
||||
|
||||
private void viewPgpKey(User user) {
|
||||
PgpEngine pgp = xmppConnectionService.getPgpEngine();
|
||||
if (pgp != null) {
|
||||
PendingIntent intent = pgp.getIntentForKey(
|
||||
conversation.getAccount(), user.getPgpKeyId());
|
||||
if (intent != null) {
|
||||
try {
|
||||
startIntentSenderForResult(intent.getIntentSender(), 0,
|
||||
null, 0, 0, 0);
|
||||
} catch (SendIntentException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,436 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract.CommonDataKinds;
|
||||
import android.provider.ContactsContract.Contacts;
|
||||
import android.provider.ContactsContract.Intents;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.QuickContactBadge;
|
||||
import android.widget.TextView;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.crypto.PgpEngine;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Presences;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
|
||||
public class ContactDetailsActivity extends XmppActivity {
|
||||
public static final String ACTION_VIEW_CONTACT = "view_contact";
|
||||
|
||||
private Contact contact;
|
||||
|
||||
private String accountJid;
|
||||
private String contactJid;
|
||||
|
||||
private TextView contactJidTv;
|
||||
private TextView accountJidTv;
|
||||
private TextView status;
|
||||
private TextView lastseen;
|
||||
private CheckBox send;
|
||||
private CheckBox receive;
|
||||
private QuickContactBadge badge;
|
||||
|
||||
private DialogInterface.OnClickListener removeFromRoster = new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
ContactDetailsActivity.this.xmppConnectionService
|
||||
.deleteContactOnServer(contact);
|
||||
ContactDetailsActivity.this.finish();
|
||||
}
|
||||
};
|
||||
|
||||
private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
|
||||
intent.setType(Contacts.CONTENT_ITEM_TYPE);
|
||||
intent.putExtra(Intents.Insert.IM_HANDLE, contact.getJid());
|
||||
intent.putExtra(Intents.Insert.IM_PROTOCOL,
|
||||
CommonDataKinds.Im.PROTOCOL_JABBER);
|
||||
intent.putExtra("finishActivityOnSaveCompleted", true);
|
||||
ContactDetailsActivity.this.startActivityForResult(intent, 0);
|
||||
}
|
||||
};
|
||||
private OnClickListener onBadgeClick = new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(
|
||||
ContactDetailsActivity.this);
|
||||
builder.setTitle(getString(R.string.action_add_phone_book));
|
||||
builder.setMessage(getString(R.string.add_phone_book_text,
|
||||
contact.getJid()));
|
||||
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||
builder.setPositiveButton(getString(R.string.add), addToPhonebook);
|
||||
builder.create().show();
|
||||
}
|
||||
};
|
||||
|
||||
private LinearLayout keys;
|
||||
|
||||
private OnRosterUpdate rosterUpdate = new OnRosterUpdate() {
|
||||
|
||||
@Override
|
||||
public void onRosterUpdate() {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
populateView();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private OnCheckedChangeListener mOnSendCheckedChange = new OnCheckedChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView,
|
||||
boolean isChecked) {
|
||||
if (isChecked) {
|
||||
if (contact
|
||||
.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
|
||||
xmppConnectionService.sendPresencePacket(contact
|
||||
.getAccount(),
|
||||
xmppConnectionService.getPresenceGenerator()
|
||||
.sendPresenceUpdatesTo(contact));
|
||||
} else {
|
||||
contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
|
||||
}
|
||||
} else {
|
||||
contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
|
||||
xmppConnectionService.sendPresencePacket(contact.getAccount(),
|
||||
xmppConnectionService.getPresenceGenerator()
|
||||
.stopPresenceUpdatesTo(contact));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private OnCheckedChangeListener mOnReceiveCheckedChange = new OnCheckedChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView,
|
||||
boolean isChecked) {
|
||||
if (isChecked) {
|
||||
xmppConnectionService.sendPresencePacket(contact.getAccount(),
|
||||
xmppConnectionService.getPresenceGenerator()
|
||||
.requestPresenceUpdatesFrom(contact));
|
||||
} else {
|
||||
xmppConnectionService.sendPresencePacket(contact.getAccount(),
|
||||
xmppConnectionService.getPresenceGenerator()
|
||||
.stopPresenceUpdatesFrom(contact));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private OnAccountUpdate accountUpdate = new OnAccountUpdate() {
|
||||
|
||||
@Override
|
||||
public void onAccountUpdate() {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
populateView();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) {
|
||||
this.accountJid = getIntent().getExtras().getString("account");
|
||||
this.contactJid = getIntent().getExtras().getString("contact");
|
||||
}
|
||||
setContentView(R.layout.activity_contact_details);
|
||||
|
||||
contactJidTv = (TextView) findViewById(R.id.details_contactjid);
|
||||
accountJidTv = (TextView) findViewById(R.id.details_account);
|
||||
status = (TextView) findViewById(R.id.details_contactstatus);
|
||||
lastseen = (TextView) findViewById(R.id.details_lastseen);
|
||||
send = (CheckBox) findViewById(R.id.details_send_presence);
|
||||
receive = (CheckBox) findViewById(R.id.details_receive_presence);
|
||||
badge = (QuickContactBadge) findViewById(R.id.details_contact_badge);
|
||||
keys = (LinearLayout) findViewById(R.id.details_contact_keys);
|
||||
getActionBar().setHomeButtonEnabled(true);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem menuItem) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||
switch (menuItem.getItemId()) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
break;
|
||||
case R.id.action_delete_contact:
|
||||
builder.setTitle(getString(R.string.action_delete_contact))
|
||||
.setMessage(
|
||||
getString(R.string.remove_contact_text,
|
||||
contact.getJid()))
|
||||
.setPositiveButton(getString(R.string.delete),
|
||||
removeFromRoster).create().show();
|
||||
break;
|
||||
case R.id.action_edit_contact:
|
||||
if (contact.getSystemAccount() == null) {
|
||||
quickEdit(contact.getDisplayName(), new OnValueEdited() {
|
||||
|
||||
@Override
|
||||
public void onValueEdited(String value) {
|
||||
contact.setServerName(value);
|
||||
ContactDetailsActivity.this.xmppConnectionService
|
||||
.pushContactToServer(contact);
|
||||
populateView();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Intent intent = new Intent(Intent.ACTION_EDIT);
|
||||
String[] systemAccount = contact.getSystemAccount().split("#");
|
||||
long id = Long.parseLong(systemAccount[0]);
|
||||
Uri uri = Contacts.getLookupUri(id, systemAccount[1]);
|
||||
intent.setDataAndType(uri, Contacts.CONTENT_ITEM_TYPE);
|
||||
intent.putExtra("finishActivityOnSaveCompleted", true);
|
||||
startActivity(intent);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return super.onOptionsItemSelected(menuItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.contact_details, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void populateView() {
|
||||
send.setOnCheckedChangeListener(null);
|
||||
receive.setOnCheckedChangeListener(null);
|
||||
setTitle(contact.getDisplayName());
|
||||
if (contact.getOption(Contact.Options.FROM)) {
|
||||
send.setText(R.string.send_presence_updates);
|
||||
send.setChecked(true);
|
||||
} else if (contact
|
||||
.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
|
||||
send.setChecked(false);
|
||||
send.setText(R.string.send_presence_updates);
|
||||
} else {
|
||||
send.setText(R.string.preemptively_grant);
|
||||
if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) {
|
||||
send.setChecked(true);
|
||||
} else {
|
||||
send.setChecked(false);
|
||||
}
|
||||
}
|
||||
if (contact.getOption(Contact.Options.TO)) {
|
||||
receive.setText(R.string.receive_presence_updates);
|
||||
receive.setChecked(true);
|
||||
} else {
|
||||
receive.setText(R.string.ask_for_presence_updates);
|
||||
if (contact.getOption(Contact.Options.ASKING)) {
|
||||
receive.setChecked(true);
|
||||
} else {
|
||||
receive.setChecked(false);
|
||||
}
|
||||
}
|
||||
if (contact.getAccount().getStatus() == Account.STATUS_ONLINE) {
|
||||
receive.setEnabled(true);
|
||||
send.setEnabled(true);
|
||||
} else {
|
||||
receive.setEnabled(false);
|
||||
send.setEnabled(false);
|
||||
}
|
||||
|
||||
send.setOnCheckedChangeListener(this.mOnSendCheckedChange);
|
||||
receive.setOnCheckedChangeListener(this.mOnReceiveCheckedChange);
|
||||
|
||||
lastseen.setText(UIHelper.lastseen(getApplicationContext(),
|
||||
contact.lastseen.time));
|
||||
|
||||
switch (contact.getMostAvailableStatus()) {
|
||||
case Presences.CHAT:
|
||||
status.setText(R.string.contact_status_free_to_chat);
|
||||
status.setTextColor(mColorGreen);
|
||||
break;
|
||||
case Presences.ONLINE:
|
||||
status.setText(R.string.contact_status_online);
|
||||
status.setTextColor(mColorGreen);
|
||||
break;
|
||||
case Presences.AWAY:
|
||||
status.setText(R.string.contact_status_away);
|
||||
status.setTextColor(mColorOrange);
|
||||
break;
|
||||
case Presences.XA:
|
||||
status.setText(R.string.contact_status_extended_away);
|
||||
status.setTextColor(mColorOrange);
|
||||
break;
|
||||
case Presences.DND:
|
||||
status.setText(R.string.contact_status_do_not_disturb);
|
||||
status.setTextColor(mColorRed);
|
||||
break;
|
||||
case Presences.OFFLINE:
|
||||
status.setText(R.string.contact_status_offline);
|
||||
status.setTextColor(mSecondaryTextColor);
|
||||
break;
|
||||
default:
|
||||
status.setText(R.string.contact_status_offline);
|
||||
status.setTextColor(mSecondaryTextColor);
|
||||
break;
|
||||
}
|
||||
if (contact.getPresences().size() > 1) {
|
||||
contactJidTv.setText(contact.getJid() + " ("
|
||||
+ contact.getPresences().size() + ")");
|
||||
} else {
|
||||
contactJidTv.setText(contact.getJid());
|
||||
}
|
||||
accountJidTv.setText(getString(R.string.using_account, contact
|
||||
.getAccount().getJid()));
|
||||
prepareContactBadge(badge, contact);
|
||||
if (contact.getSystemAccount() == null) {
|
||||
badge.setOnClickListener(onBadgeClick);
|
||||
}
|
||||
|
||||
keys.removeAllViews();
|
||||
boolean hasKeys = false;
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
for (Iterator<String> iterator = contact.getOtrFingerprints()
|
||||
.iterator(); iterator.hasNext();) {
|
||||
hasKeys = true;
|
||||
final String otrFingerprint = iterator.next();
|
||||
View view = (View) inflater.inflate(R.layout.contact_key, keys,
|
||||
false);
|
||||
TextView key = (TextView) view.findViewById(R.id.key);
|
||||
TextView keyType = (TextView) view.findViewById(R.id.key_type);
|
||||
ImageButton remove = (ImageButton) view
|
||||
.findViewById(R.id.button_remove);
|
||||
remove.setVisibility(View.VISIBLE);
|
||||
keyType.setText("OTR Fingerprint");
|
||||
key.setText(otrFingerprint);
|
||||
keys.addView(view);
|
||||
remove.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
confirmToDeleteFingerprint(otrFingerprint);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (contact.getPgpKeyId() != 0) {
|
||||
hasKeys = true;
|
||||
View view = (View) inflater.inflate(R.layout.contact_key, keys,
|
||||
false);
|
||||
TextView key = (TextView) view.findViewById(R.id.key);
|
||||
TextView keyType = (TextView) view.findViewById(R.id.key_type);
|
||||
keyType.setText("PGP Key ID");
|
||||
key.setText(OpenPgpUtils.convertKeyIdToHex(contact.getPgpKeyId()));
|
||||
view.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
PgpEngine pgp = ContactDetailsActivity.this.xmppConnectionService
|
||||
.getPgpEngine();
|
||||
if (pgp != null) {
|
||||
PendingIntent intent = pgp.getIntentForKey(contact);
|
||||
if (intent != null) {
|
||||
try {
|
||||
startIntentSenderForResult(
|
||||
intent.getIntentSender(), 0, null, 0,
|
||||
0, 0);
|
||||
} catch (SendIntentException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
keys.addView(view);
|
||||
}
|
||||
if (hasKeys) {
|
||||
keys.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
keys.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareContactBadge(QuickContactBadge badge, Contact contact) {
|
||||
if (contact.getSystemAccount() != null) {
|
||||
String[] systemAccount = contact.getSystemAccount().split("#");
|
||||
long id = Long.parseLong(systemAccount[0]);
|
||||
badge.assignContactUri(Contacts.getLookupUri(id, systemAccount[1]));
|
||||
}
|
||||
badge.setImageBitmap(avatarService().get(contact, getPixel(72)));
|
||||
}
|
||||
|
||||
protected void confirmToDeleteFingerprint(final String fingerprint) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.delete_fingerprint);
|
||||
builder.setMessage(R.string.sure_delete_fingerprint);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setPositiveButton(R.string.delete,
|
||||
new android.content.DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (contact.deleteOtrFingerprint(fingerprint)) {
|
||||
populateView();
|
||||
xmppConnectionService.syncRosterToDisk(contact
|
||||
.getAccount());
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackendConnected() {
|
||||
xmppConnectionService.setOnRosterUpdateListener(this.rosterUpdate);
|
||||
xmppConnectionService
|
||||
.setOnAccountListChangedListener(this.accountUpdate);
|
||||
if ((accountJid != null) && (contactJid != null)) {
|
||||
Account account = xmppConnectionService
|
||||
.findAccountByJid(accountJid);
|
||||
if (account == null) {
|
||||
return;
|
||||
}
|
||||
this.contact = account.getRoster().getContact(contactJid);
|
||||
populateView();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
xmppConnectionService.removeOnRosterUpdateListener();
|
||||
xmppConnectionService.removeOnAccountListChangedListener();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,947 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
|
||||
import eu.siacs.conversations.ui.adapter.ConversationAdapter;
|
||||
import eu.siacs.conversations.utils.ExceptionHelper;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.MediaStore;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActionBar;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.content.Intent;
|
||||
import android.support.v4.widget.SlidingPaneLayout;
|
||||
import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ListView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.PopupMenu.OnMenuItemClickListener;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class ConversationActivity extends XmppActivity implements
|
||||
OnAccountUpdate, OnConversationUpdate, OnRosterUpdate {
|
||||
|
||||
public static final String VIEW_CONVERSATION = "viewConversation";
|
||||
public static final String CONVERSATION = "conversationUuid";
|
||||
public static final String TEXT = "text";
|
||||
public static final String PRESENCE = "eu.siacs.conversations.presence";
|
||||
|
||||
public static final int REQUEST_SEND_MESSAGE = 0x0201;
|
||||
public static final int REQUEST_DECRYPT_PGP = 0x0202;
|
||||
private static final int REQUEST_ATTACH_FILE_DIALOG = 0x0203;
|
||||
private static final int REQUEST_IMAGE_CAPTURE = 0x0204;
|
||||
private static final int REQUEST_RECORD_AUDIO = 0x0205;
|
||||
private static final int REQUEST_SEND_PGP_IMAGE = 0x0206;
|
||||
public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207;
|
||||
|
||||
private static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301;
|
||||
private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302;
|
||||
private static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0303;
|
||||
private static final String STATE_OPEN_CONVERSATION = "state_open_conversation";
|
||||
private static final String STATE_PANEL_OPEN = "state_panel_open";
|
||||
|
||||
private String mOpenConverstaion = null;
|
||||
private boolean mPanelOpen = true;
|
||||
|
||||
private View mContentView;
|
||||
|
||||
private List<Conversation> conversationList = new ArrayList<Conversation>();
|
||||
private Conversation selectedConversation = null;
|
||||
private ListView listView;
|
||||
|
||||
private boolean paneShouldBeOpen = true;
|
||||
private ArrayAdapter<Conversation> listAdapter;
|
||||
|
||||
private Toast prepareImageToast;
|
||||
|
||||
private Uri pendingImageUri = null;
|
||||
|
||||
public List<Conversation> getConversationList() {
|
||||
return this.conversationList;
|
||||
}
|
||||
|
||||
public Conversation getSelectedConversation() {
|
||||
return this.selectedConversation;
|
||||
}
|
||||
|
||||
public void setSelectedConversation(Conversation conversation) {
|
||||
this.selectedConversation = conversation;
|
||||
}
|
||||
|
||||
public ListView getConversationListView() {
|
||||
return this.listView;
|
||||
}
|
||||
|
||||
public boolean shouldPaneBeOpen() {
|
||||
return paneShouldBeOpen;
|
||||
}
|
||||
|
||||
public void showConversationsOverview() {
|
||||
if (mContentView instanceof SlidingPaneLayout) {
|
||||
SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
|
||||
mSlidingPaneLayout.openPane();
|
||||
}
|
||||
}
|
||||
|
||||
public void hideConversationsOverview() {
|
||||
if (mContentView instanceof SlidingPaneLayout) {
|
||||
SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
|
||||
mSlidingPaneLayout.closePane();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConversationsOverviewHideable() {
|
||||
if (mContentView instanceof SlidingPaneLayout) {
|
||||
SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
|
||||
return mSlidingPaneLayout.isSlideable();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConversationsOverviewVisable() {
|
||||
if (mContentView instanceof SlidingPaneLayout) {
|
||||
SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
|
||||
return mSlidingPaneLayout.isOpen();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
mOpenConverstaion = savedInstanceState.getString(
|
||||
STATE_OPEN_CONVERSATION, null);
|
||||
mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true);
|
||||
}
|
||||
|
||||
setContentView(R.layout.fragment_conversations_overview);
|
||||
|
||||
listView = (ListView) findViewById(R.id.list);
|
||||
|
||||
getActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
getActionBar().setHomeButtonEnabled(false);
|
||||
|
||||
this.listAdapter = new ConversationAdapter(this, conversationList);
|
||||
listView.setAdapter(this.listAdapter);
|
||||
|
||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> arg0, View clickedView,
|
||||
int position, long arg3) {
|
||||
paneShouldBeOpen = false;
|
||||
if (getSelectedConversation() != conversationList.get(position)) {
|
||||
setSelectedConversation(conversationList.get(position));
|
||||
swapConversationFragment();
|
||||
} else {
|
||||
hideConversationsOverview();
|
||||
}
|
||||
}
|
||||
});
|
||||
mContentView = findViewById(R.id.content_view_spl);
|
||||
if (mContentView == null) {
|
||||
mContentView = findViewById(R.id.content_view_ll);
|
||||
}
|
||||
if (mContentView instanceof SlidingPaneLayout) {
|
||||
SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
|
||||
mSlidingPaneLayout.setParallaxDistance(150);
|
||||
mSlidingPaneLayout
|
||||
.setShadowResource(R.drawable.es_slidingpane_shadow);
|
||||
mSlidingPaneLayout.setSliderFadeColor(0);
|
||||
mSlidingPaneLayout.setPanelSlideListener(new PanelSlideListener() {
|
||||
|
||||
@Override
|
||||
public void onPanelOpened(View arg0) {
|
||||
paneShouldBeOpen = true;
|
||||
ActionBar ab = getActionBar();
|
||||
if (ab != null) {
|
||||
ab.setDisplayHomeAsUpEnabled(false);
|
||||
ab.setHomeButtonEnabled(false);
|
||||
ab.setTitle(R.string.app_name);
|
||||
}
|
||||
invalidateOptionsMenu();
|
||||
hideKeyboard();
|
||||
if (xmppConnectionServiceBound) {
|
||||
xmppConnectionService.getNotificationService()
|
||||
.setOpenConversation(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelClosed(View arg0) {
|
||||
paneShouldBeOpen = false;
|
||||
if ((conversationList.size() > 0)
|
||||
&& (getSelectedConversation() != null)) {
|
||||
openConversation(getSelectedConversation());
|
||||
if (!getSelectedConversation().isRead()) {
|
||||
xmppConnectionService.markRead(
|
||||
getSelectedConversation(), true);
|
||||
listView.invalidateViews();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelSlide(View arg0, float arg1) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void openConversation(Conversation conversation) {
|
||||
ActionBar ab = getActionBar();
|
||||
if (ab != null) {
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
ab.setHomeButtonEnabled(true);
|
||||
if (getSelectedConversation().getMode() == Conversation.MODE_SINGLE
|
||||
|| ConversationActivity.this
|
||||
.useSubjectToIdentifyConference()) {
|
||||
ab.setTitle(getSelectedConversation().getName());
|
||||
} else {
|
||||
ab.setTitle(getSelectedConversation().getContactJid()
|
||||
.split("/")[0]);
|
||||
}
|
||||
}
|
||||
invalidateOptionsMenu();
|
||||
if (xmppConnectionServiceBound) {
|
||||
xmppConnectionService.getNotificationService().setOpenConversation(
|
||||
conversation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.conversations, menu);
|
||||
MenuItem menuSecure = (MenuItem) menu.findItem(R.id.action_security);
|
||||
MenuItem menuArchive = (MenuItem) menu.findItem(R.id.action_archive);
|
||||
MenuItem menuMucDetails = (MenuItem) menu
|
||||
.findItem(R.id.action_muc_details);
|
||||
MenuItem menuContactDetails = (MenuItem) menu
|
||||
.findItem(R.id.action_contact_details);
|
||||
MenuItem menuAttach = (MenuItem) menu.findItem(R.id.action_attach_file);
|
||||
MenuItem menuClearHistory = (MenuItem) menu
|
||||
.findItem(R.id.action_clear_history);
|
||||
MenuItem menuAdd = (MenuItem) menu.findItem(R.id.action_add);
|
||||
MenuItem menuInviteContact = (MenuItem) menu
|
||||
.findItem(R.id.action_invite);
|
||||
MenuItem menuMute = (MenuItem) menu.findItem(R.id.action_mute);
|
||||
|
||||
if (isConversationsOverviewVisable()
|
||||
&& isConversationsOverviewHideable()) {
|
||||
menuArchive.setVisible(false);
|
||||
menuMucDetails.setVisible(false);
|
||||
menuContactDetails.setVisible(false);
|
||||
menuSecure.setVisible(false);
|
||||
menuInviteContact.setVisible(false);
|
||||
menuAttach.setVisible(false);
|
||||
menuClearHistory.setVisible(false);
|
||||
menuMute.setVisible(false);
|
||||
} else {
|
||||
menuAdd.setVisible(!isConversationsOverviewHideable());
|
||||
if (this.getSelectedConversation() != null) {
|
||||
if (this.getSelectedConversation().getLatestMessage()
|
||||
.getEncryption() != Message.ENCRYPTION_NONE) {
|
||||
menuSecure.setIcon(R.drawable.ic_action_secure);
|
||||
}
|
||||
if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) {
|
||||
menuContactDetails.setVisible(false);
|
||||
menuAttach.setVisible(false);
|
||||
} else {
|
||||
menuMucDetails.setVisible(false);
|
||||
menuInviteContact.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void selectPresenceToAttachFile(final int attachmentChoice) {
|
||||
selectPresence(getSelectedConversation(), new OnPresenceSelected() {
|
||||
|
||||
@Override
|
||||
public void onPresenceSelected() {
|
||||
if (attachmentChoice == ATTACHMENT_CHOICE_TAKE_PHOTO) {
|
||||
pendingImageUri = xmppConnectionService.getFileBackend()
|
||||
.getTakePhotoUri();
|
||||
Intent takePictureIntent = new Intent(
|
||||
MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
|
||||
pendingImageUri);
|
||||
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
|
||||
startActivityForResult(takePictureIntent,
|
||||
REQUEST_IMAGE_CAPTURE);
|
||||
}
|
||||
} else if (attachmentChoice == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
|
||||
Intent attachFileIntent = new Intent();
|
||||
attachFileIntent.setType("image/*");
|
||||
attachFileIntent.setAction(Intent.ACTION_GET_CONTENT);
|
||||
Intent chooser = Intent.createChooser(attachFileIntent,
|
||||
getString(R.string.attach_file));
|
||||
startActivityForResult(chooser, REQUEST_ATTACH_FILE_DIALOG);
|
||||
} else if (attachmentChoice == ATTACHMENT_CHOICE_RECORD_VOICE) {
|
||||
Intent intent = new Intent(
|
||||
MediaStore.Audio.Media.RECORD_SOUND_ACTION);
|
||||
startActivityForResult(intent, REQUEST_RECORD_AUDIO);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void attachFile(final int attachmentChoice) {
|
||||
final Conversation conversation = getSelectedConversation();
|
||||
if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
|
||||
if (hasPgp()) {
|
||||
if (conversation.getContact().getPgpKeyId() != 0) {
|
||||
xmppConnectionService.getPgpEngine().hasKey(
|
||||
conversation.getContact(),
|
||||
new UiCallback<Contact>() {
|
||||
|
||||
@Override
|
||||
public void userInputRequried(PendingIntent pi,
|
||||
Contact contact) {
|
||||
ConversationActivity.this.runIntent(pi,
|
||||
attachmentChoice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success(Contact contact) {
|
||||
selectPresenceToAttachFile(attachmentChoice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int error, Contact contact) {
|
||||
displayErrorDialog(error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
|
||||
.findFragmentByTag("conversation");
|
||||
if (fragment != null) {
|
||||
fragment.showNoPGPKeyDialog(false,
|
||||
new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
conversation
|
||||
.setNextEncryption(Message.ENCRYPTION_NONE);
|
||||
xmppConnectionService.databaseBackend
|
||||
.updateConversation(conversation);
|
||||
selectPresenceToAttachFile(attachmentChoice);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showInstallPgpDialog();
|
||||
}
|
||||
} else if (getSelectedConversation().getNextEncryption(
|
||||
forceEncryption()) == Message.ENCRYPTION_NONE) {
|
||||
selectPresenceToAttachFile(attachmentChoice);
|
||||
} else {
|
||||
selectPresenceToAttachFile(attachmentChoice);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
showConversationsOverview();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_add) {
|
||||
startActivity(new Intent(this, StartConversationActivity.class));
|
||||
return true;
|
||||
} else if (getSelectedConversation() != null) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_attach_file:
|
||||
attachFileDialog();
|
||||
break;
|
||||
case R.id.action_archive:
|
||||
this.endConversation(getSelectedConversation());
|
||||
break;
|
||||
case R.id.action_contact_details:
|
||||
Contact contact = this.getSelectedConversation().getContact();
|
||||
if (contact.showInRoster()) {
|
||||
switchToContactDetails(contact);
|
||||
} else {
|
||||
showAddToRosterDialog(getSelectedConversation());
|
||||
}
|
||||
break;
|
||||
case R.id.action_muc_details:
|
||||
Intent intent = new Intent(this,
|
||||
ConferenceDetailsActivity.class);
|
||||
intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
|
||||
intent.putExtra("uuid", getSelectedConversation().getUuid());
|
||||
startActivity(intent);
|
||||
break;
|
||||
case R.id.action_invite:
|
||||
inviteToConversation(getSelectedConversation());
|
||||
break;
|
||||
case R.id.action_security:
|
||||
selectEncryptionDialog(getSelectedConversation());
|
||||
break;
|
||||
case R.id.action_clear_history:
|
||||
clearHistoryDialog(getSelectedConversation());
|
||||
break;
|
||||
case R.id.action_mute:
|
||||
muteConversationDialog(getSelectedConversation());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void endConversation(Conversation conversation) {
|
||||
conversation.setStatus(Conversation.STATUS_ARCHIVED);
|
||||
paneShouldBeOpen = true;
|
||||
showConversationsOverview();
|
||||
xmppConnectionService.archiveConversation(conversation);
|
||||
if (conversationList.size() > 0) {
|
||||
setSelectedConversation(conversationList.get(0));
|
||||
} else {
|
||||
setSelectedConversation(null);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
protected void clearHistoryDialog(final Conversation conversation) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(getString(R.string.clear_conversation_history));
|
||||
View dialogView = getLayoutInflater().inflate(
|
||||
R.layout.dialog_clear_history, null);
|
||||
final CheckBox endConversationCheckBox = (CheckBox) dialogView
|
||||
.findViewById(R.id.end_conversation_checkbox);
|
||||
builder.setView(dialogView);
|
||||
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||
builder.setPositiveButton(getString(R.string.delete_messages),
|
||||
new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
ConversationActivity.this.xmppConnectionService
|
||||
.clearConversationHistory(conversation);
|
||||
if (endConversationCheckBox.isChecked()) {
|
||||
endConversation(conversation);
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
protected void attachFileDialog() {
|
||||
View menuAttachFile = findViewById(R.id.action_attach_file);
|
||||
if (menuAttachFile == null) {
|
||||
return;
|
||||
}
|
||||
PopupMenu attachFilePopup = new PopupMenu(this, menuAttachFile);
|
||||
attachFilePopup.inflate(R.menu.attachment_choices);
|
||||
attachFilePopup
|
||||
.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.attach_choose_picture:
|
||||
attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE);
|
||||
break;
|
||||
case R.id.attach_take_picture:
|
||||
attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO);
|
||||
break;
|
||||
case R.id.attach_record_voice:
|
||||
attachFile(ATTACHMENT_CHOICE_RECORD_VOICE);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
attachFilePopup.show();
|
||||
}
|
||||
|
||||
protected void selectEncryptionDialog(final Conversation conversation) {
|
||||
View menuItemView = findViewById(R.id.action_security);
|
||||
if (menuItemView == null) {
|
||||
return;
|
||||
}
|
||||
PopupMenu popup = new PopupMenu(this, menuItemView);
|
||||
final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
|
||||
.findFragmentByTag("conversation");
|
||||
if (fragment != null) {
|
||||
popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.encryption_choice_none:
|
||||
conversation.setNextEncryption(Message.ENCRYPTION_NONE);
|
||||
item.setChecked(true);
|
||||
break;
|
||||
case R.id.encryption_choice_otr:
|
||||
conversation.setNextEncryption(Message.ENCRYPTION_OTR);
|
||||
item.setChecked(true);
|
||||
break;
|
||||
case R.id.encryption_choice_pgp:
|
||||
if (hasPgp()) {
|
||||
if (conversation.getAccount().getKeys()
|
||||
.has("pgp_signature")) {
|
||||
conversation
|
||||
.setNextEncryption(Message.ENCRYPTION_PGP);
|
||||
item.setChecked(true);
|
||||
} else {
|
||||
announcePgp(conversation.getAccount(),
|
||||
conversation);
|
||||
}
|
||||
} else {
|
||||
showInstallPgpDialog();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
conversation.setNextEncryption(Message.ENCRYPTION_NONE);
|
||||
break;
|
||||
}
|
||||
xmppConnectionService.databaseBackend
|
||||
.updateConversation(conversation);
|
||||
fragment.updateChatMsgHint();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
popup.inflate(R.menu.encryption_choices);
|
||||
MenuItem otr = popup.getMenu().findItem(R.id.encryption_choice_otr);
|
||||
MenuItem none = popup.getMenu().findItem(
|
||||
R.id.encryption_choice_none);
|
||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||
otr.setEnabled(false);
|
||||
} else {
|
||||
if (forceEncryption()) {
|
||||
none.setVisible(false);
|
||||
}
|
||||
}
|
||||
switch (conversation.getNextEncryption(forceEncryption())) {
|
||||
case Message.ENCRYPTION_NONE:
|
||||
none.setChecked(true);
|
||||
break;
|
||||
case Message.ENCRYPTION_OTR:
|
||||
otr.setChecked(true);
|
||||
break;
|
||||
case Message.ENCRYPTION_PGP:
|
||||
popup.getMenu().findItem(R.id.encryption_choice_pgp)
|
||||
.setChecked(true);
|
||||
break;
|
||||
default:
|
||||
popup.getMenu().findItem(R.id.encryption_choice_none)
|
||||
.setChecked(true);
|
||||
break;
|
||||
}
|
||||
popup.show();
|
||||
}
|
||||
}
|
||||
|
||||
protected void muteConversationDialog(final Conversation conversation) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.disable_notifications_for_this_conversation);
|
||||
final int[] durations = getResources().getIntArray(
|
||||
R.array.mute_options_durations);
|
||||
builder.setItems(R.array.mute_options_descriptions,
|
||||
new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
long till;
|
||||
if (durations[which] == -1) {
|
||||
till = Long.MAX_VALUE;
|
||||
} else {
|
||||
till = SystemClock.elapsedRealtime()
|
||||
+ (durations[which] * 1000);
|
||||
}
|
||||
conversation.setMutedTill(till);
|
||||
ConversationActivity.this.xmppConnectionService.databaseBackend
|
||||
.updateConversation(conversation);
|
||||
ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
|
||||
.findFragmentByTag("conversation");
|
||||
if (selectedFragment != null) {
|
||||
selectedFragment.updateMessages();
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
protected ConversationFragment swapConversationFragment() {
|
||||
ConversationFragment selectedFragment = new ConversationFragment();
|
||||
if (!isFinishing()) {
|
||||
|
||||
FragmentTransaction transaction = getFragmentManager()
|
||||
.beginTransaction();
|
||||
transaction.replace(R.id.selected_conversation, selectedFragment,
|
||||
"conversation");
|
||||
try {
|
||||
transaction.commitAllowingStateLoss();
|
||||
} catch (IllegalStateException e) {
|
||||
return selectedFragment;
|
||||
}
|
||||
}
|
||||
return selectedFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (!isConversationsOverviewVisable()) {
|
||||
showConversationsOverview();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
if (xmppConnectionServiceBound) {
|
||||
if ((Intent.ACTION_VIEW.equals(intent.getAction()) && (VIEW_CONVERSATION
|
||||
.equals(intent.getType())))) {
|
||||
String convToView = (String) intent.getExtras().get(
|
||||
CONVERSATION);
|
||||
updateConversationList();
|
||||
for (int i = 0; i < conversationList.size(); ++i) {
|
||||
if (conversationList.get(i).getUuid().equals(convToView)) {
|
||||
setSelectedConversation(conversationList.get(i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
paneShouldBeOpen = false;
|
||||
String text = intent.getExtras().getString(TEXT, null);
|
||||
swapConversationFragment().setText(text);
|
||||
}
|
||||
} else {
|
||||
handledViewIntent = false;
|
||||
setIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if (this.xmppConnectionServiceBound) {
|
||||
this.onBackendConnected();
|
||||
}
|
||||
if (conversationList.size() >= 1) {
|
||||
this.onConversationUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
if (xmppConnectionServiceBound) {
|
||||
xmppConnectionService.removeOnConversationListChangedListener();
|
||||
xmppConnectionService.removeOnAccountListChangedListener();
|
||||
xmppConnectionService.removeOnRosterUpdateListener();
|
||||
xmppConnectionService.getNotificationService().setOpenConversation(
|
||||
null);
|
||||
}
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle savedInstanceState) {
|
||||
Conversation conversation = getSelectedConversation();
|
||||
if (conversation != null) {
|
||||
savedInstanceState.putString(STATE_OPEN_CONVERSATION,
|
||||
conversation.getUuid());
|
||||
}
|
||||
savedInstanceState.putBoolean(STATE_PANEL_OPEN,
|
||||
isConversationsOverviewVisable());
|
||||
super.onSaveInstanceState(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onBackendConnected() {
|
||||
this.registerListener();
|
||||
updateConversationList();
|
||||
|
||||
if (xmppConnectionService.getAccounts().size() == 0) {
|
||||
startActivity(new Intent(this, EditAccountActivity.class));
|
||||
} else if (conversationList.size() <= 0) {
|
||||
startActivity(new Intent(this, StartConversationActivity.class));
|
||||
finish();
|
||||
} else if (mOpenConverstaion != null) {
|
||||
selectConversationByUuid(mOpenConverstaion);
|
||||
paneShouldBeOpen = mPanelOpen;
|
||||
if (paneShouldBeOpen) {
|
||||
showConversationsOverview();
|
||||
}
|
||||
swapConversationFragment();
|
||||
mOpenConverstaion = null;
|
||||
} else if (getIntent() != null
|
||||
&& VIEW_CONVERSATION.equals(getIntent().getType())) {
|
||||
String uuid = (String) getIntent().getExtras().get(CONVERSATION);
|
||||
String text = getIntent().getExtras().getString(TEXT, null);
|
||||
selectConversationByUuid(uuid);
|
||||
paneShouldBeOpen = false;
|
||||
swapConversationFragment().setText(text);
|
||||
setIntent(null);
|
||||
} else {
|
||||
showConversationsOverview();
|
||||
ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
|
||||
.findFragmentByTag("conversation");
|
||||
if (selectedFragment != null) {
|
||||
selectedFragment.onBackendConnected();
|
||||
} else {
|
||||
pendingImageUri = null;
|
||||
setSelectedConversation(conversationList.get(0));
|
||||
swapConversationFragment();
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingImageUri != null) {
|
||||
attachImageToConversation(getSelectedConversation(),
|
||||
pendingImageUri);
|
||||
pendingImageUri = null;
|
||||
}
|
||||
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
|
||||
}
|
||||
|
||||
private void selectConversationByUuid(String uuid) {
|
||||
for (int i = 0; i < conversationList.size(); ++i) {
|
||||
if (conversationList.get(i).getUuid().equals(uuid)) {
|
||||
setSelectedConversation(conversationList.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void registerListener() {
|
||||
xmppConnectionService.setOnConversationListChangedListener(this);
|
||||
xmppConnectionService.setOnAccountListChangedListener(this);
|
||||
xmppConnectionService.setOnRosterUpdateListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode,
|
||||
final Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (resultCode == RESULT_OK) {
|
||||
if (requestCode == REQUEST_DECRYPT_PGP) {
|
||||
ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
|
||||
.findFragmentByTag("conversation");
|
||||
if (selectedFragment != null) {
|
||||
selectedFragment.hideSnackbar();
|
||||
selectedFragment.updateMessages();
|
||||
}
|
||||
} else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) {
|
||||
pendingImageUri = data.getData();
|
||||
if (xmppConnectionServiceBound) {
|
||||
attachImageToConversation(getSelectedConversation(),
|
||||
pendingImageUri);
|
||||
pendingImageUri = null;
|
||||
}
|
||||
} else if (requestCode == REQUEST_SEND_PGP_IMAGE) {
|
||||
|
||||
} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
|
||||
attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE);
|
||||
} else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
|
||||
attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO);
|
||||
} else if (requestCode == REQUEST_ANNOUNCE_PGP) {
|
||||
announcePgp(getSelectedConversation().getAccount(),
|
||||
getSelectedConversation());
|
||||
} else if (requestCode == REQUEST_ENCRYPT_MESSAGE) {
|
||||
// encryptTextMessage();
|
||||
} else if (requestCode == REQUEST_IMAGE_CAPTURE) {
|
||||
if (xmppConnectionServiceBound) {
|
||||
attachImageToConversation(getSelectedConversation(),
|
||||
pendingImageUri);
|
||||
pendingImageUri = null;
|
||||
}
|
||||
Intent intent = new Intent(
|
||||
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||
intent.setData(pendingImageUri);
|
||||
sendBroadcast(intent);
|
||||
} else if (requestCode == REQUEST_RECORD_AUDIO) {
|
||||
attachAudioToConversation(getSelectedConversation(),
|
||||
data.getData());
|
||||
}
|
||||
} else {
|
||||
if (requestCode == REQUEST_IMAGE_CAPTURE) {
|
||||
pendingImageUri = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void attachAudioToConversation(Conversation conversation, Uri uri) {
|
||||
|
||||
}
|
||||
|
||||
private void attachImageToConversation(Conversation conversation, Uri uri) {
|
||||
prepareImageToast = Toast.makeText(getApplicationContext(),
|
||||
getText(R.string.preparing_image), Toast.LENGTH_LONG);
|
||||
prepareImageToast.show();
|
||||
xmppConnectionService.attachImageToConversation(conversation, uri,
|
||||
new UiCallback<Message>() {
|
||||
|
||||
@Override
|
||||
public void userInputRequried(PendingIntent pi,
|
||||
Message object) {
|
||||
hidePrepareImageToast();
|
||||
ConversationActivity.this.runIntent(pi,
|
||||
ConversationActivity.REQUEST_SEND_PGP_IMAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success(Message message) {
|
||||
xmppConnectionService.sendMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int error, Message message) {
|
||||
hidePrepareImageToast();
|
||||
displayErrorDialog(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void hidePrepareImageToast() {
|
||||
if (prepareImageToast != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
prepareImageToast.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void updateConversationList() {
|
||||
xmppConnectionService
|
||||
.populateWithOrderedConversations(conversationList);
|
||||
listAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void runIntent(PendingIntent pi, int requestCode) {
|
||||
try {
|
||||
this.startIntentSenderForResult(pi.getIntentSender(), requestCode,
|
||||
null, 0, 0, 0);
|
||||
} catch (SendIntentException e1) {
|
||||
}
|
||||
}
|
||||
|
||||
public void encryptTextMessage(Message message) {
|
||||
xmppConnectionService.getPgpEngine().encrypt(message,
|
||||
new UiCallback<Message>() {
|
||||
|
||||
@Override
|
||||
public void userInputRequried(PendingIntent pi,
|
||||
Message message) {
|
||||
ConversationActivity.this.runIntent(pi,
|
||||
ConversationActivity.REQUEST_SEND_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success(Message message) {
|
||||
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
||||
xmppConnectionService.sendMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int error, Message message) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean forceEncryption() {
|
||||
return getPreferences().getBoolean("force_encryption", false);
|
||||
}
|
||||
|
||||
public boolean useSendButtonToIndicateStatus() {
|
||||
return getPreferences().getBoolean("send_button_status", false);
|
||||
}
|
||||
|
||||
public boolean indicateReceived() {
|
||||
return getPreferences().getBoolean("indicate_received", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccountUpdate() {
|
||||
final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
|
||||
.findFragmentByTag("conversation");
|
||||
if (fragment != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
fragment.updateMessages();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConversationUpdate() {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
updateConversationList();
|
||||
if (paneShouldBeOpen) {
|
||||
if (conversationList.size() >= 1) {
|
||||
swapConversationFragment();
|
||||
} else {
|
||||
startActivity(new Intent(getApplicationContext(),
|
||||
StartConversationActivity.class));
|
||||
finish();
|
||||
}
|
||||
}
|
||||
ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
|
||||
.findFragmentByTag("conversation");
|
||||
if (selectedFragment != null) {
|
||||
selectedFragment.updateMessages();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRosterUpdate() {
|
||||
final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
|
||||
.findFragmentByTag("conversation");
|
||||
if (fragment != null) {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
fragment.updateMessages();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,781 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import net.java.otr4j.session.SessionStatus;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.crypto.PgpEngine;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.MucOptions;
|
||||
import eu.siacs.conversations.entities.Presences;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.ui.EditMessage.OnEnterPressed;
|
||||
import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected;
|
||||
import eu.siacs.conversations.ui.XmppActivity.OnValueEdited;
|
||||
import eu.siacs.conversations.ui.adapter.MessageAdapter;
|
||||
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked;
|
||||
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Fragment;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.Selection;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AbsListView.OnScrollListener;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
import android.widget.AbsListView;
|
||||
|
||||
import android.widget.ListView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class ConversationFragment extends Fragment {
|
||||
|
||||
protected Conversation conversation;
|
||||
protected ListView messagesView;
|
||||
protected LayoutInflater inflater;
|
||||
protected List<Message> messageList = new ArrayList<Message>();
|
||||
protected MessageAdapter messageListAdapter;
|
||||
protected Contact contact;
|
||||
|
||||
protected String queuedPqpMessage = null;
|
||||
|
||||
private EditMessage mEditMessage;
|
||||
private ImageButton mSendButton;
|
||||
private String pastedText = null;
|
||||
private RelativeLayout snackbar;
|
||||
private TextView snackbarMessage;
|
||||
private TextView snackbarAction;
|
||||
|
||||
private boolean messagesLoaded = false;
|
||||
|
||||
private IntentSender askForPassphraseIntent = null;
|
||||
|
||||
private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<Message>();
|
||||
private boolean mDecryptJobRunning = false;
|
||||
|
||||
private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() {
|
||||
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEND) {
|
||||
InputMethodManager imm = (InputMethodManager) v.getContext()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
sendMessage();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private OnClickListener mSendButtonListener = new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
sendMessage();
|
||||
}
|
||||
};
|
||||
protected OnClickListener clickToDecryptListener = new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (activity.hasPgp() && askForPassphraseIntent != null) {
|
||||
try {
|
||||
getActivity().startIntentSenderForResult(
|
||||
askForPassphraseIntent,
|
||||
ConversationActivity.REQUEST_DECRYPT_PGP, null, 0,
|
||||
0, 0);
|
||||
} catch (SendIntentException e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private OnClickListener clickToMuc = new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(getActivity(),
|
||||
ConferenceDetailsActivity.class);
|
||||
intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
|
||||
intent.putExtra("uuid", conversation.getUuid());
|
||||
startActivity(intent);
|
||||
}
|
||||
};
|
||||
|
||||
private OnClickListener leaveMuc = new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
activity.endConversation(conversation);
|
||||
}
|
||||
};
|
||||
|
||||
private OnClickListener joinMuc = new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
activity.xmppConnectionService.joinMuc(conversation);
|
||||
}
|
||||
};
|
||||
|
||||
private OnClickListener enterPassword = new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
MucOptions muc = conversation.getMucOptions();
|
||||
String password = muc.getPassword();
|
||||
if (password == null) {
|
||||
password = "";
|
||||
}
|
||||
activity.quickPasswordEdit(password, new OnValueEdited() {
|
||||
|
||||
@Override
|
||||
public void onValueEdited(String value) {
|
||||
activity.xmppConnectionService.providePasswordForMuc(
|
||||
conversation, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private OnScrollListener mOnScrollListener = new OnScrollListener() {
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(AbsListView view, int scrollState) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(AbsListView view, int firstVisibleItem,
|
||||
int visibleItemCount, int totalItemCount) {
|
||||
if (firstVisibleItem == 0 && messagesLoaded) {
|
||||
long timestamp = messageList.get(0).getTimeSent();
|
||||
messagesLoaded = false;
|
||||
int size = activity.xmppConnectionService.loadMoreMessages(
|
||||
conversation, timestamp);
|
||||
messageList.clear();
|
||||
messageList.addAll(conversation.getMessages());
|
||||
updateStatusMessages();
|
||||
messageListAdapter.notifyDataSetChanged();
|
||||
if (size != 0) {
|
||||
messagesLoaded = true;
|
||||
}
|
||||
messagesView.setSelectionFromTop(size + 1, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private ConversationActivity activity;
|
||||
|
||||
private void sendMessage() {
|
||||
if (this.conversation == null) {
|
||||
return;
|
||||
}
|
||||
if (mEditMessage.getText().length() < 1) {
|
||||
if (this.conversation.getMode() == Conversation.MODE_MULTI) {
|
||||
conversation.setNextPresence(null);
|
||||
updateChatMsgHint();
|
||||
}
|
||||
return;
|
||||
}
|
||||
Message message = new Message(conversation, mEditMessage.getText()
|
||||
.toString(), conversation.getNextEncryption(activity
|
||||
.forceEncryption()));
|
||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||
if (conversation.getNextPresence() != null) {
|
||||
message.setPresence(conversation.getNextPresence());
|
||||
message.setType(Message.TYPE_PRIVATE);
|
||||
conversation.setNextPresence(null);
|
||||
}
|
||||
}
|
||||
if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_OTR) {
|
||||
sendOtrMessage(message);
|
||||
} else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_PGP) {
|
||||
sendPgpMessage(message);
|
||||
} else {
|
||||
sendPlainTextMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateChatMsgHint() {
|
||||
if (conversation.getMode() == Conversation.MODE_MULTI
|
||||
&& conversation.getNextPresence() != null) {
|
||||
this.mEditMessage.setHint(getString(
|
||||
R.string.send_private_message_to,
|
||||
conversation.getNextPresence()));
|
||||
} else {
|
||||
switch (conversation.getNextEncryption(activity.forceEncryption())) {
|
||||
case Message.ENCRYPTION_NONE:
|
||||
mEditMessage
|
||||
.setHint(getString(R.string.send_plain_text_message));
|
||||
break;
|
||||
case Message.ENCRYPTION_OTR:
|
||||
mEditMessage.setHint(getString(R.string.send_otr_message));
|
||||
break;
|
||||
case Message.ENCRYPTION_PGP:
|
||||
mEditMessage.setHint(getString(R.string.send_pgp_message));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.fragment_conversation,
|
||||
container, false);
|
||||
mEditMessage = (EditMessage) view.findViewById(R.id.textinput);
|
||||
mEditMessage.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
activity.hideConversationsOverview();
|
||||
}
|
||||
});
|
||||
mEditMessage.setOnEditorActionListener(mEditorActionListener);
|
||||
mEditMessage.setOnEnterPressedListener(new OnEnterPressed() {
|
||||
|
||||
@Override
|
||||
public void onEnterPressed() {
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
|
||||
mSendButton = (ImageButton) view.findViewById(R.id.textSendButton);
|
||||
mSendButton.setOnClickListener(this.mSendButtonListener);
|
||||
|
||||
snackbar = (RelativeLayout) view.findViewById(R.id.snackbar);
|
||||
snackbarMessage = (TextView) view.findViewById(R.id.snackbar_message);
|
||||
snackbarAction = (TextView) view.findViewById(R.id.snackbar_action);
|
||||
|
||||
messagesView = (ListView) view.findViewById(R.id.messages_view);
|
||||
messagesView.setOnScrollListener(mOnScrollListener);
|
||||
messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
|
||||
messageListAdapter = new MessageAdapter(
|
||||
(ConversationActivity) getActivity(), this.messageList);
|
||||
messageListAdapter
|
||||
.setOnContactPictureClicked(new OnContactPictureClicked() {
|
||||
|
||||
@Override
|
||||
public void onContactPictureClicked(Message message) {
|
||||
if (message.getStatus() <= Message.STATUS_RECEIVED) {
|
||||
if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
|
||||
if (message.getPresence() != null) {
|
||||
highlightInConference(message.getPresence());
|
||||
} else {
|
||||
highlightInConference(message
|
||||
.getCounterpart());
|
||||
}
|
||||
} else {
|
||||
Contact contact = message.getConversation()
|
||||
.getContact();
|
||||
if (contact.showInRoster()) {
|
||||
activity.switchToContactDetails(contact);
|
||||
} else {
|
||||
activity.showAddToRosterDialog(message
|
||||
.getConversation());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
messageListAdapter
|
||||
.setOnContactPictureLongClicked(new OnContactPictureLongClicked() {
|
||||
|
||||
@Override
|
||||
public void onContactPictureLongClicked(Message message) {
|
||||
if (message.getStatus() <= Message.STATUS_RECEIVED) {
|
||||
if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
|
||||
if (message.getPresence() != null) {
|
||||
privateMessageWith(message.getPresence());
|
||||
} else {
|
||||
privateMessageWith(message.getCounterpart());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
messagesView.setAdapter(messageListAdapter);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
protected void privateMessageWith(String counterpart) {
|
||||
this.mEditMessage.setText("");
|
||||
this.conversation.setNextPresence(counterpart);
|
||||
updateChatMsgHint();
|
||||
}
|
||||
|
||||
protected void highlightInConference(String nick) {
|
||||
String oldString = mEditMessage.getText().toString().trim();
|
||||
if (oldString.isEmpty() || mEditMessage.getSelectionStart() == 0) {
|
||||
mEditMessage.getText().insert(0, nick + ": ");
|
||||
} else {
|
||||
if (mEditMessage.getText().charAt(
|
||||
mEditMessage.getSelectionStart() - 1) != ' ') {
|
||||
nick = " " + nick;
|
||||
}
|
||||
mEditMessage.getText().insert(mEditMessage.getSelectionStart(),
|
||||
nick + " ");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
this.activity = (ConversationActivity) getActivity();
|
||||
if (activity.xmppConnectionServiceBound) {
|
||||
this.onBackendConnected();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
mDecryptJobRunning = false;
|
||||
super.onStop();
|
||||
if (this.conversation != null) {
|
||||
this.conversation.setNextMessage(mEditMessage.getText().toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void onBackendConnected() {
|
||||
this.activity = (ConversationActivity) getActivity();
|
||||
this.conversation = activity.getSelectedConversation();
|
||||
if (this.conversation == null) {
|
||||
return;
|
||||
}
|
||||
String oldString = conversation.getNextMessage().trim();
|
||||
if (this.pastedText == null) {
|
||||
this.mEditMessage.setText(oldString);
|
||||
} else {
|
||||
|
||||
if (oldString.isEmpty()) {
|
||||
mEditMessage.setText(pastedText);
|
||||
} else {
|
||||
mEditMessage.setText(oldString + " " + pastedText);
|
||||
}
|
||||
pastedText = null;
|
||||
}
|
||||
int position = mEditMessage.length();
|
||||
Editable etext = mEditMessage.getText();
|
||||
Selection.setSelection(etext, position);
|
||||
if (activity.isConversationsOverviewHideable()) {
|
||||
if (!activity.shouldPaneBeOpen()) {
|
||||
activity.hideConversationsOverview();
|
||||
activity.openConversation(conversation);
|
||||
}
|
||||
}
|
||||
if (this.conversation.getMode() == Conversation.MODE_MULTI) {
|
||||
conversation.setNextPresence(null);
|
||||
}
|
||||
updateMessages();
|
||||
}
|
||||
|
||||
public void updateMessages() {
|
||||
if (getView() == null) {
|
||||
return;
|
||||
}
|
||||
hideSnackbar();
|
||||
final ConversationActivity activity = (ConversationActivity) getActivity();
|
||||
if (this.conversation != null) {
|
||||
final Contact contact = this.conversation.getContact();
|
||||
if (this.conversation.isMuted()) {
|
||||
showSnackbar(R.string.notifications_disabled, R.string.enable,
|
||||
new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
conversation.setMutedTill(0);
|
||||
activity.xmppConnectionService.databaseBackend
|
||||
.updateConversation(conversation);
|
||||
updateMessages();
|
||||
}
|
||||
});
|
||||
} else if (!contact.showInRoster()
|
||||
&& contact
|
||||
.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
|
||||
showSnackbar(R.string.contact_added_you, R.string.add_back,
|
||||
new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
activity.xmppConnectionService
|
||||
.createContact(contact);
|
||||
activity.switchToContactDetails(contact);
|
||||
}
|
||||
});
|
||||
}
|
||||
for (Message message : this.conversation.getMessages()) {
|
||||
if ((message.getEncryption() == Message.ENCRYPTION_PGP)
|
||||
&& ((message.getStatus() == Message.STATUS_RECEIVED) || (message
|
||||
.getStatus() == Message.STATUS_SEND))) {
|
||||
if (!mEncryptedMessages.contains(message)) {
|
||||
mEncryptedMessages.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
decryptNext();
|
||||
this.messageList.clear();
|
||||
if (this.conversation.getMessages().size() == 0) {
|
||||
messagesLoaded = false;
|
||||
} else {
|
||||
this.messageList.addAll(this.conversation.getMessages());
|
||||
messagesLoaded = true;
|
||||
updateStatusMessages();
|
||||
}
|
||||
this.messageListAdapter.notifyDataSetChanged();
|
||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||
if (messageList.size() >= 1) {
|
||||
makeFingerprintWarning(conversation.getLatestEncryption());
|
||||
}
|
||||
} else {
|
||||
if (!conversation.getMucOptions().online()
|
||||
&& conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
|
||||
int error = conversation.getMucOptions().getError();
|
||||
switch (error) {
|
||||
case MucOptions.ERROR_NICK_IN_USE:
|
||||
showSnackbar(R.string.nick_in_use, R.string.edit,
|
||||
clickToMuc);
|
||||
break;
|
||||
case MucOptions.ERROR_ROOM_NOT_FOUND:
|
||||
showSnackbar(R.string.conference_not_found,
|
||||
R.string.leave, leaveMuc);
|
||||
break;
|
||||
case MucOptions.ERROR_PASSWORD_REQUIRED:
|
||||
showSnackbar(R.string.conference_requires_password,
|
||||
R.string.enter_password, enterPassword);
|
||||
break;
|
||||
case MucOptions.ERROR_BANNED:
|
||||
showSnackbar(R.string.conference_banned,
|
||||
R.string.leave, leaveMuc);
|
||||
break;
|
||||
case MucOptions.ERROR_MEMBERS_ONLY:
|
||||
showSnackbar(R.string.conference_members_only,
|
||||
R.string.leave, leaveMuc);
|
||||
break;
|
||||
case MucOptions.KICKED_FROM_ROOM:
|
||||
showSnackbar(R.string.conference_kicked, R.string.join,
|
||||
joinMuc);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
getActivity().invalidateOptionsMenu();
|
||||
updateChatMsgHint();
|
||||
if (!activity.shouldPaneBeOpen()) {
|
||||
activity.xmppConnectionService.markRead(conversation, true);
|
||||
activity.updateConversationList();
|
||||
}
|
||||
this.updateSendButton();
|
||||
}
|
||||
}
|
||||
|
||||
private void decryptNext() {
|
||||
Message next = this.mEncryptedMessages.peek();
|
||||
PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
|
||||
|
||||
if (next != null && engine != null && !mDecryptJobRunning) {
|
||||
mDecryptJobRunning = true;
|
||||
engine.decrypt(next, new UiCallback<Message>() {
|
||||
|
||||
@Override
|
||||
public void userInputRequried(PendingIntent pi, Message message) {
|
||||
mDecryptJobRunning = false;
|
||||
askForPassphraseIntent = pi.getIntentSender();
|
||||
showSnackbar(R.string.openpgp_messages_found,
|
||||
R.string.decrypt, clickToDecryptListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success(Message message) {
|
||||
mDecryptJobRunning = false;
|
||||
mEncryptedMessages.remove();
|
||||
activity.xmppConnectionService.updateMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int error, Message message) {
|
||||
message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
|
||||
mDecryptJobRunning = false;
|
||||
mEncryptedMessages.remove();
|
||||
activity.xmppConnectionService.updateConversationUi();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void messageSent() {
|
||||
int size = this.messageList.size();
|
||||
messagesView.setSelection(size - 1);
|
||||
mEditMessage.setText("");
|
||||
updateChatMsgHint();
|
||||
}
|
||||
|
||||
public void updateSendButton() {
|
||||
Conversation c = this.conversation;
|
||||
if (activity.useSendButtonToIndicateStatus() && c != null
|
||||
&& c.getAccount().getStatus() == Account.STATUS_ONLINE) {
|
||||
if (c.getMode() == Conversation.MODE_SINGLE) {
|
||||
switch (c.getContact().getMostAvailableStatus()) {
|
||||
case Presences.CHAT:
|
||||
this.mSendButton
|
||||
.setImageResource(R.drawable.ic_action_send_now_online);
|
||||
break;
|
||||
case Presences.ONLINE:
|
||||
this.mSendButton
|
||||
.setImageResource(R.drawable.ic_action_send_now_online);
|
||||
break;
|
||||
case Presences.AWAY:
|
||||
this.mSendButton
|
||||
.setImageResource(R.drawable.ic_action_send_now_away);
|
||||
break;
|
||||
case Presences.XA:
|
||||
this.mSendButton
|
||||
.setImageResource(R.drawable.ic_action_send_now_away);
|
||||
break;
|
||||
case Presences.DND:
|
||||
this.mSendButton
|
||||
.setImageResource(R.drawable.ic_action_send_now_dnd);
|
||||
break;
|
||||
default:
|
||||
this.mSendButton
|
||||
.setImageResource(R.drawable.ic_action_send_now_offline);
|
||||
break;
|
||||
}
|
||||
} else if (c.getMode() == Conversation.MODE_MULTI) {
|
||||
if (c.getMucOptions().online()) {
|
||||
this.mSendButton
|
||||
.setImageResource(R.drawable.ic_action_send_now_online);
|
||||
} else {
|
||||
this.mSendButton
|
||||
.setImageResource(R.drawable.ic_action_send_now_offline);
|
||||
}
|
||||
} else {
|
||||
this.mSendButton
|
||||
.setImageResource(R.drawable.ic_action_send_now_offline);
|
||||
}
|
||||
} else {
|
||||
this.mSendButton
|
||||
.setImageResource(R.drawable.ic_action_send_now_offline);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateStatusMessages() {
|
||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||
for (int i = this.messageList.size() - 1; i >= 0; --i) {
|
||||
if (this.messageList.get(i).getStatus() == Message.STATUS_RECEIVED) {
|
||||
return;
|
||||
} else {
|
||||
if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
|
||||
this.messageList.add(i + 1,
|
||||
Message.createStatusMessage(conversation));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void makeFingerprintWarning(int latestEncryption) {
|
||||
Set<String> knownFingerprints = conversation.getContact()
|
||||
.getOtrFingerprints();
|
||||
if ((latestEncryption == Message.ENCRYPTION_OTR)
|
||||
&& (conversation.hasValidOtrSession()
|
||||
&& (!conversation.isMuted())
|
||||
&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
|
||||
.contains(conversation.getOtrFingerprint())))) {
|
||||
showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify,
|
||||
new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (conversation.getOtrFingerprint() != null) {
|
||||
AlertDialog dialog = UIHelper
|
||||
.getVerifyFingerprintDialog(
|
||||
(ConversationActivity) getActivity(),
|
||||
conversation, snackbar);
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected void showSnackbar(int message, int action,
|
||||
OnClickListener clickListener) {
|
||||
snackbar.setVisibility(View.VISIBLE);
|
||||
snackbar.setOnClickListener(null);
|
||||
snackbarMessage.setText(message);
|
||||
snackbarMessage.setOnClickListener(null);
|
||||
snackbarAction.setText(action);
|
||||
snackbarAction.setOnClickListener(clickListener);
|
||||
}
|
||||
|
||||
protected void hideSnackbar() {
|
||||
snackbar.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
protected void sendPlainTextMessage(Message message) {
|
||||
ConversationActivity activity = (ConversationActivity) getActivity();
|
||||
activity.xmppConnectionService.sendMessage(message);
|
||||
messageSent();
|
||||
}
|
||||
|
||||
protected void sendPgpMessage(final Message message) {
|
||||
final ConversationActivity activity = (ConversationActivity) getActivity();
|
||||
final XmppConnectionService xmppService = activity.xmppConnectionService;
|
||||
final Contact contact = message.getConversation().getContact();
|
||||
if (activity.hasPgp()) {
|
||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||
if (contact.getPgpKeyId() != 0) {
|
||||
xmppService.getPgpEngine().hasKey(contact,
|
||||
new UiCallback<Contact>() {
|
||||
|
||||
@Override
|
||||
public void userInputRequried(PendingIntent pi,
|
||||
Contact contact) {
|
||||
activity.runIntent(
|
||||
pi,
|
||||
ConversationActivity.REQUEST_ENCRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success(Contact contact) {
|
||||
messageSent();
|
||||
activity.encryptTextMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int error, Contact contact) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
showNoPGPKeyDialog(false,
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
conversation
|
||||
.setNextEncryption(Message.ENCRYPTION_NONE);
|
||||
xmppService.databaseBackend
|
||||
.updateConversation(conversation);
|
||||
message.setEncryption(Message.ENCRYPTION_NONE);
|
||||
xmppService.sendMessage(message);
|
||||
messageSent();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (conversation.getMucOptions().pgpKeysInUse()) {
|
||||
if (!conversation.getMucOptions().everybodyHasKeys()) {
|
||||
Toast warning = Toast
|
||||
.makeText(getActivity(),
|
||||
R.string.missing_public_keys,
|
||||
Toast.LENGTH_LONG);
|
||||
warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
|
||||
warning.show();
|
||||
}
|
||||
activity.encryptTextMessage(message);
|
||||
messageSent();
|
||||
} else {
|
||||
showNoPGPKeyDialog(true,
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
conversation
|
||||
.setNextEncryption(Message.ENCRYPTION_NONE);
|
||||
message.setEncryption(Message.ENCRYPTION_NONE);
|
||||
xmppService.databaseBackend
|
||||
.updateConversation(conversation);
|
||||
xmppService.sendMessage(message);
|
||||
messageSent();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
activity.showInstallPgpDialog();
|
||||
}
|
||||
}
|
||||
|
||||
public void showNoPGPKeyDialog(boolean plural,
|
||||
DialogInterface.OnClickListener listener) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setIconAttribute(android.R.attr.alertDialogIcon);
|
||||
if (plural) {
|
||||
builder.setTitle(getString(R.string.no_pgp_keys));
|
||||
builder.setMessage(getText(R.string.contacts_have_no_pgp_keys));
|
||||
} else {
|
||||
builder.setTitle(getString(R.string.no_pgp_key));
|
||||
builder.setMessage(getText(R.string.contact_has_no_pgp_key));
|
||||
}
|
||||
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||
builder.setPositiveButton(getString(R.string.send_unencrypted),
|
||||
listener);
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
protected void sendOtrMessage(final Message message) {
|
||||
final ConversationActivity activity = (ConversationActivity) getActivity();
|
||||
final XmppConnectionService xmppService = activity.xmppConnectionService;
|
||||
if (conversation.hasValidOtrSession()) {
|
||||
activity.xmppConnectionService.sendMessage(message);
|
||||
messageSent();
|
||||
} else {
|
||||
activity.selectPresence(message.getConversation(),
|
||||
new OnPresenceSelected() {
|
||||
|
||||
@Override
|
||||
public void onPresenceSelected() {
|
||||
message.setPresence(conversation.getNextPresence());
|
||||
xmppService.sendMessage(message);
|
||||
messageSent();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.pastedText = text;
|
||||
}
|
||||
|
||||
public void clearInputField() {
|
||||
this.mEditMessage.setText("");
|
||||
}
|
||||
}
|
@ -0,0 +1,423 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
|
||||
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
import eu.siacs.conversations.utils.Validator;
|
||||
import eu.siacs.conversations.xmpp.XmppConnection.Features;
|
||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||
|
||||
public class EditAccountActivity extends XmppActivity {
|
||||
|
||||
private AutoCompleteTextView mAccountJid;
|
||||
private EditText mPassword;
|
||||
private EditText mPasswordConfirm;
|
||||
private CheckBox mRegisterNew;
|
||||
private Button mCancelButton;
|
||||
private Button mSaveButton;
|
||||
|
||||
private LinearLayout mStats;
|
||||
private TextView mServerInfoSm;
|
||||
private TextView mServerInfoCarbons;
|
||||
private TextView mServerInfoPep;
|
||||
private TextView mSessionEst;
|
||||
private TextView mOtrFingerprint;
|
||||
private RelativeLayout mOtrFingerprintBox;
|
||||
private ImageButton mOtrFingerprintToClipboardButton;
|
||||
|
||||
private String jidToEdit;
|
||||
private Account mAccount;
|
||||
|
||||
private boolean mFetchingAvatar = false;
|
||||
|
||||
private OnClickListener mSaveButtonClickListener = new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mAccount != null
|
||||
&& mAccount.getStatus() == Account.STATUS_DISABLED) {
|
||||
mAccount.setOption(Account.OPTION_DISABLED, false);
|
||||
xmppConnectionService.updateAccount(mAccount);
|
||||
return;
|
||||
}
|
||||
if (!Validator.isValidJid(mAccountJid.getText().toString())) {
|
||||
mAccountJid.setError(getString(R.string.invalid_jid));
|
||||
mAccountJid.requestFocus();
|
||||
return;
|
||||
}
|
||||
boolean registerNewAccount = mRegisterNew.isChecked();
|
||||
String[] jidParts = mAccountJid.getText().toString().split("@");
|
||||
String username = jidParts[0];
|
||||
String server;
|
||||
if (jidParts.length >= 2) {
|
||||
server = jidParts[1];
|
||||
} else {
|
||||
server = "";
|
||||
}
|
||||
String password = mPassword.getText().toString();
|
||||
String passwordConfirm = mPasswordConfirm.getText().toString();
|
||||
if (registerNewAccount) {
|
||||
if (!password.equals(passwordConfirm)) {
|
||||
mPasswordConfirm
|
||||
.setError(getString(R.string.passwords_do_not_match));
|
||||
mPasswordConfirm.requestFocus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (mAccount != null) {
|
||||
mAccount.setPassword(password);
|
||||
mAccount.setUsername(username);
|
||||
mAccount.setServer(server);
|
||||
mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
|
||||
xmppConnectionService.updateAccount(mAccount);
|
||||
} else {
|
||||
if (xmppConnectionService.findAccountByJid(mAccountJid
|
||||
.getText().toString()) != null) {
|
||||
mAccountJid
|
||||
.setError(getString(R.string.account_already_exists));
|
||||
mAccountJid.requestFocus();
|
||||
return;
|
||||
}
|
||||
mAccount = new Account(username, server, password);
|
||||
mAccount.setOption(Account.OPTION_USETLS, true);
|
||||
mAccount.setOption(Account.OPTION_USECOMPRESSION, true);
|
||||
mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
|
||||
xmppConnectionService.createAccount(mAccount);
|
||||
}
|
||||
if (jidToEdit != null) {
|
||||
finish();
|
||||
} else {
|
||||
updateSaveButton();
|
||||
updateAccountInformation();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
private OnClickListener mCancelButtonClickListener = new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
};
|
||||
private OnAccountUpdate mOnAccountUpdateListener = new OnAccountUpdate() {
|
||||
|
||||
@Override
|
||||
public void onAccountUpdate() {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (mAccount != null
|
||||
&& mAccount.getStatus() != Account.STATUS_ONLINE
|
||||
&& mFetchingAvatar) {
|
||||
startActivity(new Intent(getApplicationContext(),
|
||||
ManageAccountActivity.class));
|
||||
finish();
|
||||
} else if (jidToEdit == null && mAccount != null
|
||||
&& mAccount.getStatus() == Account.STATUS_ONLINE) {
|
||||
if (!mFetchingAvatar) {
|
||||
mFetchingAvatar = true;
|
||||
xmppConnectionService.checkForAvatar(mAccount,
|
||||
mAvatarFetchCallback);
|
||||
}
|
||||
} else {
|
||||
updateSaveButton();
|
||||
}
|
||||
if (mAccount != null) {
|
||||
updateAccountInformation();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
private UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() {
|
||||
|
||||
@Override
|
||||
public void userInputRequried(PendingIntent pi, Avatar avatar) {
|
||||
finishInitialSetup(avatar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success(Avatar avatar) {
|
||||
finishInitialSetup(avatar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int errorCode, Avatar avatar) {
|
||||
finishInitialSetup(avatar);
|
||||
}
|
||||
};
|
||||
private KnownHostsAdapter mKnownHostsAdapter;
|
||||
private TextWatcher mTextWatcher = new TextWatcher() {
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before,
|
||||
int count) {
|
||||
updateSaveButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||
int after) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
protected void finishInitialSetup(final Avatar avatar) {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Intent intent;
|
||||
if (avatar != null) {
|
||||
intent = new Intent(getApplicationContext(),
|
||||
StartConversationActivity.class);
|
||||
} else {
|
||||
intent = new Intent(getApplicationContext(),
|
||||
PublishProfilePictureActivity.class);
|
||||
intent.putExtra("account", mAccount.getJid());
|
||||
intent.putExtra("setup", true);
|
||||
}
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected boolean inputDataDiffersFromAccount() {
|
||||
if (mAccount == null) {
|
||||
return true;
|
||||
} else {
|
||||
return (!mAccount.getJid().equals(mAccountJid.getText().toString()))
|
||||
|| (!mAccount.getPassword().equals(
|
||||
mPassword.getText().toString()) || mAccount
|
||||
.isOptionSet(Account.OPTION_REGISTER) != mRegisterNew
|
||||
.isChecked());
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateSaveButton() {
|
||||
if (mAccount != null
|
||||
&& mAccount.getStatus() == Account.STATUS_CONNECTING) {
|
||||
this.mSaveButton.setEnabled(false);
|
||||
this.mSaveButton.setTextColor(getSecondaryTextColor());
|
||||
this.mSaveButton.setText(R.string.account_status_connecting);
|
||||
} else if (mAccount != null
|
||||
&& mAccount.getStatus() == Account.STATUS_DISABLED) {
|
||||
this.mSaveButton.setEnabled(true);
|
||||
this.mSaveButton.setTextColor(getPrimaryTextColor());
|
||||
this.mSaveButton.setText(R.string.enable);
|
||||
} else {
|
||||
this.mSaveButton.setEnabled(true);
|
||||
this.mSaveButton.setTextColor(getPrimaryTextColor());
|
||||
if (jidToEdit != null) {
|
||||
if (mAccount != null
|
||||
&& mAccount.getStatus() == Account.STATUS_ONLINE) {
|
||||
this.mSaveButton.setText(R.string.save);
|
||||
if (!accountInfoEdited()) {
|
||||
this.mSaveButton.setEnabled(false);
|
||||
this.mSaveButton.setTextColor(getSecondaryTextColor());
|
||||
}
|
||||
} else {
|
||||
this.mSaveButton.setText(R.string.connect);
|
||||
}
|
||||
} else {
|
||||
this.mSaveButton.setText(R.string.next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean accountInfoEdited() {
|
||||
return (!this.mAccount.getJid().equals(
|
||||
this.mAccountJid.getText().toString()))
|
||||
|| (!this.mAccount.getPassword().equals(
|
||||
this.mPassword.getText().toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_edit_account);
|
||||
this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid);
|
||||
this.mAccountJid.addTextChangedListener(this.mTextWatcher);
|
||||
this.mPassword = (EditText) findViewById(R.id.account_password);
|
||||
this.mPassword.addTextChangedListener(this.mTextWatcher);
|
||||
this.mPasswordConfirm = (EditText) findViewById(R.id.account_password_confirm);
|
||||
this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new);
|
||||
this.mStats = (LinearLayout) findViewById(R.id.stats);
|
||||
this.mSessionEst = (TextView) findViewById(R.id.session_est);
|
||||
this.mServerInfoCarbons = (TextView) findViewById(R.id.server_info_carbons);
|
||||
this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm);
|
||||
this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep);
|
||||
this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint);
|
||||
this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box);
|
||||
this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard);
|
||||
this.mSaveButton = (Button) findViewById(R.id.save_button);
|
||||
this.mCancelButton = (Button) findViewById(R.id.cancel_button);
|
||||
this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
|
||||
this.mCancelButton.setOnClickListener(this.mCancelButtonClickListener);
|
||||
this.mRegisterNew
|
||||
.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView,
|
||||
boolean isChecked) {
|
||||
if (isChecked) {
|
||||
mPasswordConfirm.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mPasswordConfirm.setVisibility(View.GONE);
|
||||
}
|
||||
updateSaveButton();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
if (getIntent() != null) {
|
||||
this.jidToEdit = getIntent().getStringExtra("jid");
|
||||
if (this.jidToEdit != null) {
|
||||
this.mRegisterNew.setVisibility(View.GONE);
|
||||
getActionBar().setTitle(jidToEdit);
|
||||
} else {
|
||||
getActionBar().setTitle(R.string.action_add_account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
if (xmppConnectionServiceBound) {
|
||||
xmppConnectionService.removeOnAccountListChangedListener();
|
||||
}
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBackendConnected() {
|
||||
this.mKnownHostsAdapter = new KnownHostsAdapter(this,
|
||||
android.R.layout.simple_list_item_1,
|
||||
xmppConnectionService.getKnownHosts());
|
||||
this.xmppConnectionService
|
||||
.setOnAccountListChangedListener(this.mOnAccountUpdateListener);
|
||||
if (this.jidToEdit != null) {
|
||||
this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit);
|
||||
updateAccountInformation();
|
||||
} else if (this.xmppConnectionService.getAccounts().size() == 0) {
|
||||
getActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
getActionBar().setDisplayShowHomeEnabled(false);
|
||||
this.mCancelButton.setEnabled(false);
|
||||
this.mCancelButton.setTextColor(getSecondaryTextColor());
|
||||
}
|
||||
this.mAccountJid.setAdapter(this.mKnownHostsAdapter);
|
||||
updateSaveButton();
|
||||
}
|
||||
|
||||
private void updateAccountInformation() {
|
||||
this.mAccountJid.setText(this.mAccount.getJid());
|
||||
this.mPassword.setText(this.mAccount.getPassword());
|
||||
if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
|
||||
this.mRegisterNew.setVisibility(View.VISIBLE);
|
||||
this.mRegisterNew.setChecked(true);
|
||||
this.mPasswordConfirm.setText(this.mAccount.getPassword());
|
||||
} else {
|
||||
this.mRegisterNew.setVisibility(View.GONE);
|
||||
this.mRegisterNew.setChecked(false);
|
||||
}
|
||||
if (this.mAccount.getStatus() == Account.STATUS_ONLINE
|
||||
&& !this.mFetchingAvatar) {
|
||||
this.mStats.setVisibility(View.VISIBLE);
|
||||
this.mSessionEst.setText(UIHelper.readableTimeDifference(
|
||||
getApplicationContext(), this.mAccount.getXmppConnection()
|
||||
.getLastSessionEstablished()));
|
||||
Features features = this.mAccount.getXmppConnection().getFeatures();
|
||||
if (features.carbons()) {
|
||||
this.mServerInfoCarbons.setText(R.string.server_info_available);
|
||||
} else {
|
||||
this.mServerInfoCarbons
|
||||
.setText(R.string.server_info_unavailable);
|
||||
}
|
||||
if (features.sm()) {
|
||||
this.mServerInfoSm.setText(R.string.server_info_available);
|
||||
} else {
|
||||
this.mServerInfoSm.setText(R.string.server_info_unavailable);
|
||||
}
|
||||
if (features.pubsub()) {
|
||||
this.mServerInfoPep.setText(R.string.server_info_available);
|
||||
} else {
|
||||
this.mServerInfoPep.setText(R.string.server_info_unavailable);
|
||||
}
|
||||
final String fingerprint = this.mAccount
|
||||
.getOtrFingerprint(xmppConnectionService);
|
||||
if (fingerprint != null) {
|
||||
this.mOtrFingerprintBox.setVisibility(View.VISIBLE);
|
||||
this.mOtrFingerprint.setText(fingerprint);
|
||||
this.mOtrFingerprintToClipboardButton
|
||||
.setVisibility(View.VISIBLE);
|
||||
this.mOtrFingerprintToClipboardButton
|
||||
.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
if (OtrFingerprintToClipBoard(fingerprint)) {
|
||||
Toast.makeText(
|
||||
EditAccountActivity.this,
|
||||
R.string.toast_message_otr_fingerprint,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.mOtrFingerprintBox.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
if (this.mAccount.errorStatus()) {
|
||||
this.mAccountJid.setError(getString(this.mAccount
|
||||
.getReadableStatusId()));
|
||||
this.mAccountJid.requestFocus();
|
||||
}
|
||||
this.mStats.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean OtrFingerprintToClipBoard(String fingerprint) {
|
||||
ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||
String label = getResources().getString(R.string.otr_fingerprint);
|
||||
if (mClipBoardManager != null) {
|
||||
ClipData mClipData = ClipData.newPlainText(label, fingerprint);
|
||||
mClipBoardManager.setPrimaryClip(mClipData);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.widget.EditText;
|
||||
|
||||
public class EditMessage extends EditText {
|
||||
|
||||
public EditMessage(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public EditMessage(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
protected OnEnterPressed mOnEnterPressed;
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||
if (mOnEnterPressed != null) {
|
||||
mOnEnterPressed.onEnterPressed();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
public void setOnEnterPressedListener(OnEnterPressed listener) {
|
||||
this.mOnEnterPressed = listener;
|
||||
}
|
||||
|
||||
public interface OnEnterPressed {
|
||||
public void onEnterPressed();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
|
||||
import eu.siacs.conversations.ui.adapter.AccountAdapter;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
|
||||
public class ManageAccountActivity extends XmppActivity {
|
||||
|
||||
protected Account selectedAccount = null;
|
||||
|
||||
protected List<Account> accountList = new ArrayList<Account>();
|
||||
protected ListView accountListView;
|
||||
protected AccountAdapter mAccountAdapter;
|
||||
protected OnAccountUpdate accountChanged = new OnAccountUpdate() {
|
||||
|
||||
@Override
|
||||
public void onAccountUpdate() {
|
||||
accountList.clear();
|
||||
accountList.addAll(xmppConnectionService.getAccounts());
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mAccountAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.manage_accounts);
|
||||
|
||||
accountListView = (ListView) findViewById(R.id.account_list);
|
||||
this.mAccountAdapter = new AccountAdapter(this, accountList);
|
||||
accountListView.setAdapter(this.mAccountAdapter);
|
||||
accountListView.setOnItemClickListener(new OnItemClickListener() {
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> arg0, View view,
|
||||
int position, long arg3) {
|
||||
switchToAccount(accountList.get(position));
|
||||
}
|
||||
});
|
||||
registerForContextMenu(accountListView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
ManageAccountActivity.this.getMenuInflater().inflate(
|
||||
R.menu.manageaccounts_context, menu);
|
||||
AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
|
||||
this.selectedAccount = accountList.get(acmi.position);
|
||||
if (this.selectedAccount.isOptionSet(Account.OPTION_DISABLED)) {
|
||||
menu.findItem(R.id.mgmt_account_disable).setVisible(false);
|
||||
menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(false);
|
||||
menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false);
|
||||
} else {
|
||||
menu.findItem(R.id.mgmt_account_enable).setVisible(false);
|
||||
}
|
||||
menu.setHeaderTitle(this.selectedAccount.getJid());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
if (xmppConnectionServiceBound) {
|
||||
xmppConnectionService.removeOnAccountListChangedListener();
|
||||
}
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
void onBackendConnected() {
|
||||
xmppConnectionService.setOnAccountListChangedListener(accountChanged);
|
||||
this.accountList.clear();
|
||||
this.accountList.addAll(xmppConnectionService.getAccounts());
|
||||
mAccountAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.manageaccounts, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.mgmt_account_publish_avatar:
|
||||
publishAvatar(selectedAccount);
|
||||
return true;
|
||||
case R.id.mgmt_account_disable:
|
||||
disableAccount(selectedAccount);
|
||||
return true;
|
||||
case R.id.mgmt_account_enable:
|
||||
enableAccount(selectedAccount);
|
||||
return true;
|
||||
case R.id.mgmt_account_delete:
|
||||
deleteAccount(selectedAccount);
|
||||
return true;
|
||||
case R.id.mgmt_account_announce_pgp:
|
||||
publishOpenPGPPublicKey(selectedAccount);
|
||||
default:
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_add_account:
|
||||
startActivity(new Intent(getApplicationContext(),
|
||||
EditAccountActivity.class));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNavigateUp() {
|
||||
if (xmppConnectionService.getConversations().size() == 0) {
|
||||
Intent contactsIntent = new Intent(this,
|
||||
StartConversationActivity.class);
|
||||
contactsIntent.setFlags(
|
||||
// if activity exists in stack, pop the stack and go back to it
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TOP |
|
||||
// otherwise, make a new task for it
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK |
|
||||
// don't use the new activity animation; finish
|
||||
// animation runs instead
|
||||
Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
startActivity(contactsIntent);
|
||||
finish();
|
||||
return true;
|
||||
} else {
|
||||
return super.onNavigateUp();
|
||||
}
|
||||
}
|
||||
|
||||
private void publishAvatar(Account account) {
|
||||
Intent intent = new Intent(getApplicationContext(),
|
||||
PublishProfilePictureActivity.class);
|
||||
intent.putExtra("account", account.getJid());
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void disableAccount(Account account) {
|
||||
account.setOption(Account.OPTION_DISABLED, true);
|
||||
xmppConnectionService.updateAccount(account);
|
||||
}
|
||||
|
||||
private void enableAccount(Account account) {
|
||||
account.setOption(Account.OPTION_DISABLED, false);
|
||||
xmppConnectionService.updateAccount(account);
|
||||
}
|
||||
|
||||
private void publishOpenPGPPublicKey(Account account) {
|
||||
if (ManageAccountActivity.this.hasPgp()) {
|
||||
announcePgp(account, null);
|
||||
} else {
|
||||
this.showInstallPgpDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteAccount(final Account account) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(
|
||||
ManageAccountActivity.this);
|
||||
builder.setTitle(getString(R.string.mgmt_account_are_you_sure));
|
||||
builder.setIconAttribute(android.R.attr.alertDialogIcon);
|
||||
builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text));
|
||||
builder.setPositiveButton(getString(R.string.delete),
|
||||
new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
xmppConnectionService.deleteAccount(account);
|
||||
selectedAccount = null;
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (resultCode == RESULT_OK) {
|
||||
if (requestCode == REQUEST_ANNOUNCE_PGP) {
|
||||
announcePgp(selectedAccount, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,242 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnLongClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.utils.PhoneHelper;
|
||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||
|
||||
public class PublishProfilePictureActivity extends XmppActivity {
|
||||
|
||||
private static final int REQUEST_CHOOSE_FILE = 0xac23;
|
||||
|
||||
private ImageView avatar;
|
||||
private TextView accountTextView;
|
||||
private TextView hintOrWarning;
|
||||
private TextView secondaryHint;
|
||||
private Button cancelButton;
|
||||
private Button publishButton;
|
||||
|
||||
private Uri avatarUri;
|
||||
private Uri defaultUri;
|
||||
|
||||
private Account account;
|
||||
|
||||
private boolean support = false;
|
||||
|
||||
private boolean mInitialAccountSetup;
|
||||
|
||||
private UiCallback<Avatar> avatarPublication = new UiCallback<Avatar>() {
|
||||
|
||||
@Override
|
||||
public void success(Avatar object) {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (mInitialAccountSetup) {
|
||||
startActivity(new Intent(getApplicationContext(),
|
||||
StartConversationActivity.class));
|
||||
}
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(final int errorCode, Avatar object) {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
hintOrWarning.setText(errorCode);
|
||||
hintOrWarning.setTextColor(getWarningTextColor());
|
||||
publishButton.setText(R.string.publish);
|
||||
enablePublishButton();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userInputRequried(PendingIntent pi, Avatar object) {
|
||||
}
|
||||
};
|
||||
|
||||
private OnLongClickListener backToDefaultListener = new OnLongClickListener() {
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
avatarUri = defaultUri;
|
||||
loadImageIntoPreview(defaultUri);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_publish_profile_picture);
|
||||
this.avatar = (ImageView) findViewById(R.id.account_image);
|
||||
this.cancelButton = (Button) findViewById(R.id.cancel_button);
|
||||
this.publishButton = (Button) findViewById(R.id.publish_button);
|
||||
this.accountTextView = (TextView) findViewById(R.id.account);
|
||||
this.hintOrWarning = (TextView) findViewById(R.id.hint_or_warning);
|
||||
this.secondaryHint = (TextView) findViewById(R.id.secondary_hint);
|
||||
this.publishButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (avatarUri != null) {
|
||||
publishButton.setText(R.string.publishing);
|
||||
disablePublishButton();
|
||||
xmppConnectionService.publishAvatar(account, avatarUri,
|
||||
avatarPublication);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.cancelButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mInitialAccountSetup) {
|
||||
startActivity(new Intent(getApplicationContext(),
|
||||
StartConversationActivity.class));
|
||||
}
|
||||
finish();
|
||||
}
|
||||
});
|
||||
this.avatar.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent attachFileIntent = new Intent();
|
||||
attachFileIntent.setType("image/*");
|
||||
attachFileIntent.setAction(Intent.ACTION_GET_CONTENT);
|
||||
Intent chooser = Intent.createChooser(attachFileIntent,
|
||||
getString(R.string.attach_file));
|
||||
startActivityForResult(chooser, REQUEST_CHOOSE_FILE);
|
||||
}
|
||||
});
|
||||
this.defaultUri = PhoneHelper.getSefliUri(getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode,
|
||||
final Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (resultCode == RESULT_OK) {
|
||||
if (requestCode == REQUEST_CHOOSE_FILE) {
|
||||
this.avatarUri = data.getData();
|
||||
if (xmppConnectionServiceBound) {
|
||||
loadImageIntoPreview(this.avatarUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBackendConnected() {
|
||||
if (getIntent() != null) {
|
||||
String jid = getIntent().getStringExtra("account");
|
||||
if (jid != null) {
|
||||
this.account = xmppConnectionService.findAccountByJid(jid);
|
||||
if (this.account.getXmppConnection() != null) {
|
||||
this.support = this.account.getXmppConnection()
|
||||
.getFeatures().pubsub();
|
||||
}
|
||||
if (this.avatarUri == null) {
|
||||
if (this.account.getAvatar() != null
|
||||
|| this.defaultUri == null) {
|
||||
this.avatar.setImageBitmap(avatarService().get(account,
|
||||
getPixel(194)));
|
||||
if (this.defaultUri != null) {
|
||||
this.avatar
|
||||
.setOnLongClickListener(this.backToDefaultListener);
|
||||
} else {
|
||||
this.secondaryHint.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
if (!support) {
|
||||
this.hintOrWarning
|
||||
.setTextColor(getWarningTextColor());
|
||||
this.hintOrWarning
|
||||
.setText(R.string.error_publish_avatar_no_server_support);
|
||||
}
|
||||
} else {
|
||||
this.avatarUri = this.defaultUri;
|
||||
loadImageIntoPreview(this.defaultUri);
|
||||
this.secondaryHint.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
} else {
|
||||
loadImageIntoPreview(avatarUri);
|
||||
}
|
||||
this.accountTextView.setText(this.account.getJid());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
if (getIntent() != null) {
|
||||
this.mInitialAccountSetup = getIntent().getBooleanExtra("setup",
|
||||
false);
|
||||
}
|
||||
if (this.mInitialAccountSetup) {
|
||||
this.cancelButton.setText(R.string.skip);
|
||||
}
|
||||
}
|
||||
|
||||
protected void loadImageIntoPreview(Uri uri) {
|
||||
Bitmap bm = xmppConnectionService.getFileBackend().cropCenterSquare(
|
||||
uri, 384);
|
||||
if (bm == null) {
|
||||
disablePublishButton();
|
||||
this.hintOrWarning.setTextColor(getWarningTextColor());
|
||||
this.hintOrWarning
|
||||
.setText(R.string.error_publish_avatar_converting);
|
||||
return;
|
||||
}
|
||||
this.avatar.setImageBitmap(bm);
|
||||
if (support) {
|
||||
enablePublishButton();
|
||||
this.publishButton.setText(R.string.publish);
|
||||
this.hintOrWarning.setText(R.string.publish_avatar_explanation);
|
||||
this.hintOrWarning.setTextColor(getPrimaryTextColor());
|
||||
} else {
|
||||
disablePublishButton();
|
||||
this.hintOrWarning.setTextColor(getWarningTextColor());
|
||||
this.hintOrWarning
|
||||
.setText(R.string.error_publish_avatar_no_server_support);
|
||||
}
|
||||
if (this.defaultUri != null && uri.equals(this.defaultUri)) {
|
||||
this.secondaryHint.setVisibility(View.INVISIBLE);
|
||||
this.avatar.setOnLongClickListener(null);
|
||||
} else if (this.defaultUri != null) {
|
||||
this.secondaryHint.setVisibility(View.VISIBLE);
|
||||
this.avatar.setOnLongClickListener(this.backToDefaultListener);
|
||||
}
|
||||
}
|
||||
|
||||
protected void enablePublishButton() {
|
||||
this.publishButton.setEnabled(true);
|
||||
this.publishButton.setTextColor(getPrimaryTextColor());
|
||||
}
|
||||
|
||||
protected void disablePublishButton() {
|
||||
this.publishButton.setEnabled(false);
|
||||
this.publishButton.setTextColor(getSecondaryTextColor());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
public class SettingsActivity extends XmppActivity implements
|
||||
OnSharedPreferenceChangeListener {
|
||||
private SettingsFragment mSettingsFragment;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mSettingsFragment = new SettingsFragment();
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, mSettingsFragment).commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
void onBackendConnected() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
ListPreference resources = (ListPreference) mSettingsFragment
|
||||
.findPreference("resource");
|
||||
if (resources != null) {
|
||||
ArrayList<CharSequence> entries = new ArrayList<CharSequence>(
|
||||
Arrays.asList(resources.getEntries()));
|
||||
entries.add(0, Build.MODEL);
|
||||
resources.setEntries(entries.toArray(new CharSequence[entries
|
||||
.size()]));
|
||||
resources.setEntryValues(entries.toArray(new CharSequence[entries
|
||||
.size()]));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences,
|
||||
String name) {
|
||||
if (name.equals("resource")) {
|
||||
String resource = preferences.getString("resource", "mobile")
|
||||
.toLowerCase(Locale.US);
|
||||
if (xmppConnectionServiceBound) {
|
||||
for (Account account : xmppConnectionService.getAccounts()) {
|
||||
account.setResource(resource);
|
||||
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
||||
xmppConnectionService.reconnectAccount(account, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceFragment;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.ui.adapter.ConversationAdapter;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class ShareWithActivity extends XmppActivity {
|
||||
|
||||
private class Share {
|
||||
public Uri uri;
|
||||
public String account;
|
||||
public String contact;
|
||||
public String text;
|
||||
}
|
||||
|
||||
private Share share;
|
||||
|
||||
private static final int REQUEST_START_NEW_CONVERSATION = 0x0501;
|
||||
private ListView mListView;
|
||||
private List<Conversation> mConversations = new ArrayList<Conversation>();
|
||||
|
||||
private UiCallback<Message> attachImageCallback = new UiCallback<Message>() {
|
||||
|
||||
@Override
|
||||
public void userInputRequried(PendingIntent pi, Message object) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success(Message message) {
|
||||
xmppConnectionService.sendMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int errorCode, Message object) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
protected void onActivityResult(int requestCode, int resultCode,
|
||||
final Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQUEST_START_NEW_CONVERSATION
|
||||
&& resultCode == RESULT_OK) {
|
||||
share.contact = data.getStringExtra("contact");
|
||||
share.account = data.getStringExtra("account");
|
||||
Log.d(Config.LOGTAG, "contact: " + share.contact + " account:"
|
||||
+ share.account);
|
||||
}
|
||||
if (xmppConnectionServiceBound && share != null
|
||||
&& share.contact != null && share.account != null) {
|
||||
share();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
getActionBar().setHomeButtonEnabled(false);
|
||||
|
||||
setContentView(R.layout.share_with);
|
||||
setTitle(getString(R.string.title_activity_sharewith));
|
||||
|
||||
mListView = (ListView) findViewById(R.id.choose_conversation_list);
|
||||
ConversationAdapter mAdapter = new ConversationAdapter(this,
|
||||
this.mConversations);
|
||||
mListView.setAdapter(mAdapter);
|
||||
mListView.setOnItemClickListener(new OnItemClickListener() {
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> arg0, View arg1,
|
||||
int position, long arg3) {
|
||||
Conversation conversation = mConversations.get(position);
|
||||
if (conversation.getMode() == Conversation.MODE_SINGLE
|
||||
|| share.uri == null) {
|
||||
share(mConversations.get(position));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.share = new Share();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.share_with, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_add:
|
||||
Intent intent = new Intent(getApplicationContext(),
|
||||
ChooseContactActivity.class);
|
||||
startActivityForResult(intent, REQUEST_START_NEW_CONVERSATION);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
if (getIntent().getType() != null
|
||||
&& getIntent().getType().startsWith("image/")) {
|
||||
this.share.uri = (Uri) getIntent().getParcelableExtra(
|
||||
Intent.EXTRA_STREAM);
|
||||
} else {
|
||||
this.share.text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
}
|
||||
if (xmppConnectionServiceBound) {
|
||||
xmppConnectionService.populateWithOrderedConversations(
|
||||
mConversations, this.share.uri == null);
|
||||
}
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
void onBackendConnected() {
|
||||
if (xmppConnectionServiceBound && share != null
|
||||
&& share.contact != null && share.account != null) {
|
||||
share();
|
||||
return;
|
||||
}
|
||||
xmppConnectionService.populateWithOrderedConversations(mConversations,
|
||||
this.share != null && this.share.uri == null);
|
||||
}
|
||||
|
||||
private void share() {
|
||||
Account account = xmppConnectionService.findAccountByJid(share.account);
|
||||
if (account == null) {
|
||||
return;
|
||||
}
|
||||
Conversation conversation = xmppConnectionService
|
||||
.findOrCreateConversation(account, share.contact, false);
|
||||
share(conversation);
|
||||
}
|
||||
|
||||
private void share(final Conversation conversation) {
|
||||
if (share.uri != null) {
|
||||
selectPresence(conversation, new OnPresenceSelected() {
|
||||
@Override
|
||||
public void onPresenceSelected() {
|
||||
Toast.makeText(getApplicationContext(),
|
||||
getText(R.string.preparing_image),
|
||||
Toast.LENGTH_LONG).show();
|
||||
ShareWithActivity.this.xmppConnectionService
|
||||
.attachImageToConversation(conversation, share.uri,
|
||||
attachImageCallback);
|
||||
switchToConversation(conversation, null, true);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
switchToConversation(conversation, this.share.text, true);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,677 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActionBar;
|
||||
import android.app.ActionBar.Tab;
|
||||
import android.app.ActionBar.TabListener;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.app.ListFragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v13.app.FragmentPagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Spinner;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Bookmark;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.ListItem;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
|
||||
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
|
||||
import eu.siacs.conversations.ui.adapter.ListItemAdapter;
|
||||
import eu.siacs.conversations.utils.Validator;
|
||||
|
||||
public class StartConversationActivity extends XmppActivity {
|
||||
|
||||
private Tab mContactsTab;
|
||||
private Tab mConferencesTab;
|
||||
private ViewPager mViewPager;
|
||||
|
||||
private MyListFragment mContactsListFragment = new MyListFragment();
|
||||
private List<ListItem> contacts = new ArrayList<ListItem>();
|
||||
private ArrayAdapter<ListItem> mContactsAdapter;
|
||||
|
||||
private MyListFragment mConferenceListFragment = new MyListFragment();
|
||||
private List<ListItem> conferences = new ArrayList<ListItem>();
|
||||
private ArrayAdapter<ListItem> mConferenceAdapter;
|
||||
|
||||
private List<String> mActivatedAccounts = new ArrayList<String>();
|
||||
private List<String> mKnownHosts;
|
||||
private List<String> mKnownConferenceHosts;
|
||||
|
||||
private Menu mOptionsMenu;
|
||||
private EditText mSearchEditText;
|
||||
|
||||
public int conference_context_id;
|
||||
public int contact_context_id;
|
||||
|
||||
private TabListener mTabListener = new TabListener() {
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabSelected(Tab tab, FragmentTransaction ft) {
|
||||
mViewPager.setCurrentItem(tab.getPosition());
|
||||
onTabChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(Tab tab, FragmentTransaction ft) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
private ViewPager.SimpleOnPageChangeListener mOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
getActionBar().setSelectedNavigationItem(position);
|
||||
onTabChanged();
|
||||
}
|
||||
};
|
||||
|
||||
private MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
||||
mSearchEditText.post(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mSearchEditText.requestFocus();
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mSearchEditText,
|
||||
InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(),
|
||||
InputMethodManager.HIDE_IMPLICIT_ONLY);
|
||||
mSearchEditText.setText("");
|
||||
filter(null);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
private TextWatcher mSearchTextWatcher = new TextWatcher() {
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
filter(editable.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||
int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before,
|
||||
int count) {
|
||||
}
|
||||
};
|
||||
private OnRosterUpdate onRosterUpdate = new OnRosterUpdate() {
|
||||
|
||||
@Override
|
||||
public void onRosterUpdate() {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (mSearchEditText != null) {
|
||||
filter(mSearchEditText.getText().toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
private MenuItem mMenuSearchView;
|
||||
private String mInitialJid;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_start_conversation);
|
||||
mViewPager = (ViewPager) findViewById(R.id.start_conversation_view_pager);
|
||||
ActionBar actionBar = getActionBar();
|
||||
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
||||
|
||||
mContactsTab = actionBar.newTab().setText(R.string.contacts)
|
||||
.setTabListener(mTabListener);
|
||||
mConferencesTab = actionBar.newTab().setText(R.string.conferences)
|
||||
.setTabListener(mTabListener);
|
||||
actionBar.addTab(mContactsTab);
|
||||
actionBar.addTab(mConferencesTab);
|
||||
|
||||
mViewPager.setOnPageChangeListener(mOnPageChangeListener);
|
||||
mViewPager.setAdapter(new FragmentPagerAdapter(getFragmentManager()) {
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
if (position == 0) {
|
||||
return mContactsListFragment;
|
||||
} else {
|
||||
return mConferenceListFragment;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mConferenceAdapter = new ListItemAdapter(this, conferences);
|
||||
mConferenceListFragment.setListAdapter(mConferenceAdapter);
|
||||
mConferenceListFragment.setContextMenu(R.menu.conference_context);
|
||||
mConferenceListFragment
|
||||
.setOnListItemClickListener(new OnItemClickListener() {
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> arg0, View arg1,
|
||||
int position, long arg3) {
|
||||
openConversationForBookmark(position);
|
||||
}
|
||||
});
|
||||
|
||||
mContactsAdapter = new ListItemAdapter(this, contacts);
|
||||
mContactsListFragment.setListAdapter(mContactsAdapter);
|
||||
mContactsListFragment.setContextMenu(R.menu.contact_context);
|
||||
mContactsListFragment
|
||||
.setOnListItemClickListener(new OnItemClickListener() {
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> arg0, View arg1,
|
||||
int position, long arg3) {
|
||||
openConversationForContact(position);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
xmppConnectionService.removeOnRosterUpdateListener();
|
||||
}
|
||||
|
||||
protected void openConversationForContact(int position) {
|
||||
Contact contact = (Contact) contacts.get(position);
|
||||
Conversation conversation = xmppConnectionService
|
||||
.findOrCreateConversation(contact.getAccount(),
|
||||
contact.getJid(), false);
|
||||
switchToConversation(conversation);
|
||||
}
|
||||
|
||||
protected void openConversationForContact() {
|
||||
int position = contact_context_id;
|
||||
openConversationForContact(position);
|
||||
}
|
||||
|
||||
protected void openConversationForBookmark() {
|
||||
openConversationForBookmark(conference_context_id);
|
||||
}
|
||||
|
||||
protected void openConversationForBookmark(int position) {
|
||||
Bookmark bookmark = (Bookmark) conferences.get(position);
|
||||
Conversation conversation = xmppConnectionService
|
||||
.findOrCreateConversation(bookmark.getAccount(),
|
||||
bookmark.getJid(), true);
|
||||
conversation.setBookmark(bookmark);
|
||||
if (!conversation.getMucOptions().online()) {
|
||||
xmppConnectionService.joinMuc(conversation);
|
||||
}
|
||||
if (!bookmark.autojoin()) {
|
||||
bookmark.setAutojoin(true);
|
||||
xmppConnectionService.pushBookmarks(bookmark.getAccount());
|
||||
}
|
||||
switchToConversation(conversation);
|
||||
}
|
||||
|
||||
protected void openDetailsForContact() {
|
||||
int position = contact_context_id;
|
||||
Contact contact = (Contact) contacts.get(position);
|
||||
switchToContactDetails(contact);
|
||||
}
|
||||
|
||||
protected void deleteContact() {
|
||||
int position = contact_context_id;
|
||||
final Contact contact = (Contact) contacts.get(position);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setTitle(R.string.action_delete_contact);
|
||||
builder.setMessage(getString(R.string.remove_contact_text,
|
||||
contact.getJid()));
|
||||
builder.setPositiveButton(R.string.delete, new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
xmppConnectionService.deleteContactOnServer(contact);
|
||||
filter(mSearchEditText.getText().toString());
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
|
||||
}
|
||||
|
||||
protected void deleteConference() {
|
||||
int position = conference_context_id;
|
||||
final Bookmark bookmark = (Bookmark) conferences.get(position);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setTitle(R.string.delete_bookmark);
|
||||
builder.setMessage(getString(R.string.remove_bookmark_text,
|
||||
bookmark.getJid()));
|
||||
builder.setPositiveButton(R.string.delete, new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
bookmark.unregisterConversation();
|
||||
Account account = bookmark.getAccount();
|
||||
account.getBookmarks().remove(bookmark);
|
||||
xmppConnectionService.pushBookmarks(account);
|
||||
filter(mSearchEditText.getText().toString());
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
protected void showCreateContactDialog(String prefilledJid) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.create_contact);
|
||||
View dialogView = getLayoutInflater().inflate(
|
||||
R.layout.create_contact_dialog, null);
|
||||
final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
|
||||
final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView
|
||||
.findViewById(R.id.jid);
|
||||
jid.setAdapter(new KnownHostsAdapter(this,
|
||||
android.R.layout.simple_list_item_1, mKnownHosts));
|
||||
if (prefilledJid != null) {
|
||||
jid.append(prefilledJid);
|
||||
}
|
||||
populateAccountSpinner(spinner);
|
||||
builder.setView(dialogView);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setPositiveButton(R.string.create, null);
|
||||
final AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
|
||||
new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (!xmppConnectionServiceBound) {
|
||||
return;
|
||||
}
|
||||
if (Validator.isValidJid(jid.getText().toString())) {
|
||||
String accountJid = (String) spinner
|
||||
.getSelectedItem();
|
||||
String contactJid = jid.getText().toString();
|
||||
Account account = xmppConnectionService
|
||||
.findAccountByJid(accountJid);
|
||||
if (account == null) {
|
||||
dialog.dismiss();
|
||||
return;
|
||||
}
|
||||
Contact contact = account.getRoster().getContact(
|
||||
contactJid);
|
||||
if (contact.showInRoster()) {
|
||||
jid.setError(getString(R.string.contact_already_exists));
|
||||
} else {
|
||||
xmppConnectionService.createContact(contact);
|
||||
dialog.dismiss();
|
||||
switchToConversation(contact);
|
||||
}
|
||||
} else {
|
||||
jid.setError(getString(R.string.invalid_jid));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
protected void showJoinConferenceDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.join_conference);
|
||||
View dialogView = getLayoutInflater().inflate(
|
||||
R.layout.join_conference_dialog, null);
|
||||
final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
|
||||
final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView
|
||||
.findViewById(R.id.jid);
|
||||
jid.setAdapter(new KnownHostsAdapter(this,
|
||||
android.R.layout.simple_list_item_1, mKnownConferenceHosts));
|
||||
populateAccountSpinner(spinner);
|
||||
final CheckBox bookmarkCheckBox = (CheckBox) dialogView
|
||||
.findViewById(R.id.bookmark);
|
||||
builder.setView(dialogView);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setPositiveButton(R.string.join, null);
|
||||
final AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
|
||||
new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (!xmppConnectionServiceBound) {
|
||||
return;
|
||||
}
|
||||
if (Validator.isValidJid(jid.getText().toString())) {
|
||||
String accountJid = (String) spinner
|
||||
.getSelectedItem();
|
||||
String conferenceJid = jid.getText().toString();
|
||||
Account account = xmppConnectionService
|
||||
.findAccountByJid(accountJid);
|
||||
if (account == null) {
|
||||
dialog.dismiss();
|
||||
return;
|
||||
}
|
||||
if (bookmarkCheckBox.isChecked()) {
|
||||
if (account.hasBookmarkFor(conferenceJid)) {
|
||||
jid.setError(getString(R.string.bookmark_already_exists));
|
||||
} else {
|
||||
Bookmark bookmark = new Bookmark(account,
|
||||
conferenceJid);
|
||||
bookmark.setAutojoin(true);
|
||||
account.getBookmarks().add(bookmark);
|
||||
xmppConnectionService
|
||||
.pushBookmarks(account);
|
||||
Conversation conversation = xmppConnectionService
|
||||
.findOrCreateConversation(account,
|
||||
conferenceJid, true);
|
||||
conversation.setBookmark(bookmark);
|
||||
if (!conversation.getMucOptions().online()) {
|
||||
xmppConnectionService
|
||||
.joinMuc(conversation);
|
||||
}
|
||||
dialog.dismiss();
|
||||
switchToConversation(conversation);
|
||||
}
|
||||
} else {
|
||||
Conversation conversation = xmppConnectionService
|
||||
.findOrCreateConversation(account,
|
||||
conferenceJid, true);
|
||||
if (!conversation.getMucOptions().online()) {
|
||||
xmppConnectionService.joinMuc(conversation);
|
||||
}
|
||||
dialog.dismiss();
|
||||
switchToConversation(conversation);
|
||||
}
|
||||
} else {
|
||||
jid.setError(getString(R.string.invalid_jid));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void switchToConversation(Contact contact) {
|
||||
Conversation conversation = xmppConnectionService
|
||||
.findOrCreateConversation(contact.getAccount(),
|
||||
contact.getJid(), false);
|
||||
switchToConversation(conversation);
|
||||
}
|
||||
|
||||
private void populateAccountSpinner(Spinner spinner) {
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, mActivatedAccounts);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
this.mOptionsMenu = menu;
|
||||
getMenuInflater().inflate(R.menu.start_conversation, menu);
|
||||
MenuItem menuCreateContact = (MenuItem) menu
|
||||
.findItem(R.id.action_create_contact);
|
||||
MenuItem menuCreateConference = (MenuItem) menu
|
||||
.findItem(R.id.action_join_conference);
|
||||
mMenuSearchView = (MenuItem) menu.findItem(R.id.action_search);
|
||||
mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
|
||||
View mSearchView = mMenuSearchView.getActionView();
|
||||
mSearchEditText = (EditText) mSearchView
|
||||
.findViewById(R.id.search_field);
|
||||
mSearchEditText.addTextChangedListener(mSearchTextWatcher);
|
||||
if (getActionBar().getSelectedNavigationIndex() == 0) {
|
||||
menuCreateConference.setVisible(false);
|
||||
} else {
|
||||
menuCreateContact.setVisible(false);
|
||||
}
|
||||
if (mInitialJid != null) {
|
||||
mMenuSearchView.expandActionView();
|
||||
mSearchEditText.append(mInitialJid);
|
||||
filter(mInitialJid);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_create_contact:
|
||||
showCreateContactDialog(null);
|
||||
break;
|
||||
case R.id.action_join_conference:
|
||||
showJoinConferenceDialog();
|
||||
break;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_SEARCH && !event.isLongPress()) {
|
||||
mOptionsMenu.findItem(R.id.action_search).expandActionView();
|
||||
return true;
|
||||
}
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBackendConnected() {
|
||||
xmppConnectionService.setOnRosterUpdateListener(this.onRosterUpdate);
|
||||
this.mActivatedAccounts.clear();
|
||||
for (Account account : xmppConnectionService.getAccounts()) {
|
||||
if (account.getStatus() != Account.STATUS_DISABLED) {
|
||||
this.mActivatedAccounts.add(account.getJid());
|
||||
}
|
||||
}
|
||||
this.mKnownHosts = xmppConnectionService.getKnownHosts();
|
||||
this.mKnownConferenceHosts = xmppConnectionService
|
||||
.getKnownConferenceHosts();
|
||||
if (!startByIntent()) {
|
||||
if (mSearchEditText != null) {
|
||||
filter(mSearchEditText.getText().toString());
|
||||
} else {
|
||||
filter(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean startByIntent() {
|
||||
if (getIntent() != null
|
||||
&& Intent.ACTION_SENDTO.equals(getIntent().getAction())) {
|
||||
try {
|
||||
String jid = URLDecoder.decode(
|
||||
getIntent().getData().getEncodedPath(), "UTF-8").split(
|
||||
"/")[1];
|
||||
setIntent(null);
|
||||
return handleJid(jid);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
setIntent(null);
|
||||
return false;
|
||||
}
|
||||
} else if (getIntent() != null
|
||||
&& Intent.ACTION_VIEW.equals(getIntent().getAction())) {
|
||||
Uri uri = getIntent().getData();
|
||||
String jid = uri.getSchemeSpecificPart().split("\\?")[0];
|
||||
return handleJid(jid);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean handleJid(String jid) {
|
||||
List<Contact> contacts = xmppConnectionService.findContacts(jid);
|
||||
if (contacts.size() == 0) {
|
||||
showCreateContactDialog(jid);
|
||||
return false;
|
||||
} else if (contacts.size() == 1) {
|
||||
switchToConversation(contacts.get(0));
|
||||
return true;
|
||||
} else {
|
||||
if (mMenuSearchView != null) {
|
||||
mMenuSearchView.expandActionView();
|
||||
mSearchEditText.setText(jid);
|
||||
filter(jid);
|
||||
} else {
|
||||
mInitialJid = jid;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected void filter(String needle) {
|
||||
if (xmppConnectionServiceBound) {
|
||||
this.filterContacts(needle);
|
||||
this.filterConferences(needle);
|
||||
}
|
||||
}
|
||||
|
||||
protected void filterContacts(String needle) {
|
||||
this.contacts.clear();
|
||||
for (Account account : xmppConnectionService.getAccounts()) {
|
||||
if (account.getStatus() != Account.STATUS_DISABLED) {
|
||||
for (Contact contact : account.getRoster().getContacts()) {
|
||||
if (contact.showInRoster() && contact.match(needle)) {
|
||||
this.contacts.add(contact);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.sort(this.contacts);
|
||||
mContactsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
protected void filterConferences(String needle) {
|
||||
this.conferences.clear();
|
||||
for (Account account : xmppConnectionService.getAccounts()) {
|
||||
if (account.getStatus() != Account.STATUS_DISABLED) {
|
||||
for (Bookmark bookmark : account.getBookmarks()) {
|
||||
if (bookmark.match(needle)) {
|
||||
this.conferences.add(bookmark);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.sort(this.conferences);
|
||||
mConferenceAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void onTabChanged() {
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
public static class MyListFragment extends ListFragment {
|
||||
private AdapterView.OnItemClickListener mOnItemClickListener;
|
||||
private int mResContextMenu;
|
||||
|
||||
public void setContextMenu(int res) {
|
||||
this.mResContextMenu = res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
if (mOnItemClickListener != null) {
|
||||
mOnItemClickListener.onItemClick(l, v, position, id);
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
|
||||
this.mOnItemClickListener = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
registerForContextMenu(getListView());
|
||||
getListView().setFastScrollEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
StartConversationActivity activity = (StartConversationActivity) getActivity();
|
||||
activity.getMenuInflater().inflate(mResContextMenu, menu);
|
||||
AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
|
||||
if (mResContextMenu == R.menu.conference_context) {
|
||||
activity.conference_context_id = acmi.position;
|
||||
} else {
|
||||
activity.contact_context_id = acmi.position;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
StartConversationActivity activity = (StartConversationActivity) getActivity();
|
||||
switch (item.getItemId()) {
|
||||
case R.id.context_start_conversation:
|
||||
activity.openConversationForContact();
|
||||
break;
|
||||
case R.id.context_contact_details:
|
||||
activity.openDetailsForContact();
|
||||
break;
|
||||
case R.id.context_delete_contact:
|
||||
activity.deleteContact();
|
||||
break;
|
||||
case R.id.context_join_conference:
|
||||
activity.openConversationForBookmark();
|
||||
break;
|
||||
case R.id.context_delete_conference:
|
||||
activity.deleteConference();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
|
||||
public interface UiCallback<T> {
|
||||
public void success(T object);
|
||||
|
||||
public void error(int errorCode, T object);
|
||||
|
||||
public void userInputRequried(PendingIntent pi, T object);
|
||||
}
|
@ -0,0 +1,637 @@
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.Presences;
|
||||
import eu.siacs.conversations.services.AvatarService;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
|
||||
import eu.siacs.conversations.utils.ExceptionHelper;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.AlertDialog.Builder;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.InputType;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
|
||||
public abstract class XmppActivity extends Activity {
|
||||
|
||||
protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;
|
||||
protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102;
|
||||
|
||||
public XmppConnectionService xmppConnectionService;
|
||||
public boolean xmppConnectionServiceBound = false;
|
||||
protected boolean handledViewIntent = false;
|
||||
|
||||
protected int mPrimaryTextColor;
|
||||
protected int mSecondaryTextColor;
|
||||
protected int mSecondaryBackgroundColor;
|
||||
protected int mColorRed;
|
||||
protected int mColorOrange;
|
||||
protected int mColorGreen;
|
||||
protected int mPrimaryColor;
|
||||
|
||||
protected boolean mUseSubject = true;
|
||||
|
||||
private DisplayMetrics metrics;
|
||||
|
||||
protected interface OnValueEdited {
|
||||
public void onValueEdited(String value);
|
||||
}
|
||||
|
||||
public interface OnPresenceSelected {
|
||||
public void onPresenceSelected();
|
||||
}
|
||||
|
||||
protected ServiceConnection mConnection = new ServiceConnection() {
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
XmppConnectionBinder binder = (XmppConnectionBinder) service;
|
||||
xmppConnectionService = binder.getService();
|
||||
xmppConnectionServiceBound = true;
|
||||
onBackendConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName arg0) {
|
||||
xmppConnectionServiceBound = false;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
if (!xmppConnectionServiceBound) {
|
||||
connectToBackend();
|
||||
}
|
||||
}
|
||||
|
||||
public void connectToBackend() {
|
||||
Intent intent = new Intent(this, XmppConnectionService.class);
|
||||
intent.setAction("ui");
|
||||
startService(intent);
|
||||
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if (xmppConnectionServiceBound) {
|
||||
unbindService(mConnection);
|
||||
xmppConnectionServiceBound = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void hideKeyboard() {
|
||||
InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
View focus = getCurrentFocus();
|
||||
|
||||
if (focus != null) {
|
||||
|
||||
inputManager.hideSoftInputFromWindow(focus.getWindowToken(),
|
||||
InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasPgp() {
|
||||
return xmppConnectionService.getPgpEngine() != null;
|
||||
}
|
||||
|
||||
public void showInstallPgpDialog() {
|
||||
Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(getString(R.string.openkeychain_required));
|
||||
builder.setIconAttribute(android.R.attr.alertDialogIcon);
|
||||
builder.setMessage(getText(R.string.openkeychain_required_long));
|
||||
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||
builder.setNeutralButton(getString(R.string.restart),
|
||||
new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (xmppConnectionServiceBound) {
|
||||
unbindService(mConnection);
|
||||
xmppConnectionServiceBound = false;
|
||||
}
|
||||
stopService(new Intent(XmppActivity.this,
|
||||
XmppConnectionService.class));
|
||||
finish();
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton(getString(R.string.install),
|
||||
new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Uri uri = Uri
|
||||
.parse("market://details?id=org.sufficientlysecure.keychain");
|
||||
Intent marketIntent = new Intent(Intent.ACTION_VIEW,
|
||||
uri);
|
||||
PackageManager manager = getApplicationContext()
|
||||
.getPackageManager();
|
||||
List<ResolveInfo> infos = manager
|
||||
.queryIntentActivities(marketIntent, 0);
|
||||
if (infos.size() > 0) {
|
||||
startActivity(marketIntent);
|
||||
} else {
|
||||
uri = Uri.parse("http://www.openkeychain.org/");
|
||||
Intent browserIntent = new Intent(
|
||||
Intent.ACTION_VIEW, uri);
|
||||
startActivity(browserIntent);
|
||||
}
|
||||
finish();
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
abstract void onBackendConnected();
|
||||
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_settings:
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
break;
|
||||
case R.id.action_accounts:
|
||||
startActivity(new Intent(this, ManageAccountActivity.class));
|
||||
break;
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
metrics = getResources().getDisplayMetrics();
|
||||
ExceptionHelper.init(getApplicationContext());
|
||||
mPrimaryTextColor = getResources().getColor(R.color.primarytext);
|
||||
mSecondaryTextColor = getResources().getColor(R.color.secondarytext);
|
||||
mColorRed = getResources().getColor(R.color.red);
|
||||
mColorOrange = getResources().getColor(R.color.orange);
|
||||
mColorGreen = getResources().getColor(R.color.green);
|
||||
mPrimaryColor = getResources().getColor(R.color.primary);
|
||||
mSecondaryBackgroundColor = getResources().getColor(
|
||||
R.color.secondarybackground);
|
||||
if (getPreferences().getBoolean("use_larger_font", false)) {
|
||||
setTheme(R.style.ConversationsTheme_LargerText);
|
||||
}
|
||||
mUseSubject = getPreferences().getBoolean("use_subject", true);
|
||||
}
|
||||
|
||||
protected SharedPreferences getPreferences() {
|
||||
return PreferenceManager
|
||||
.getDefaultSharedPreferences(getApplicationContext());
|
||||
}
|
||||
|
||||
public boolean useSubjectToIdentifyConference() {
|
||||
return mUseSubject;
|
||||
}
|
||||
|
||||
public void switchToConversation(Conversation conversation) {
|
||||
switchToConversation(conversation, null, false);
|
||||
}
|
||||
|
||||
public void switchToConversation(Conversation conversation, String text,
|
||||
boolean newTask) {
|
||||
Intent viewConversationIntent = new Intent(this,
|
||||
ConversationActivity.class);
|
||||
viewConversationIntent.setAction(Intent.ACTION_VIEW);
|
||||
viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
|
||||
conversation.getUuid());
|
||||
if (text != null) {
|
||||
viewConversationIntent.putExtra(ConversationActivity.TEXT, text);
|
||||
}
|
||||
viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION);
|
||||
if (newTask) {
|
||||
viewConversationIntent.setFlags(viewConversationIntent.getFlags()
|
||||
| Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
} else {
|
||||
viewConversationIntent.setFlags(viewConversationIntent.getFlags()
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
}
|
||||
startActivity(viewConversationIntent);
|
||||
finish();
|
||||
}
|
||||
|
||||
public void switchToContactDetails(Contact contact) {
|
||||
Intent intent = new Intent(this, ContactDetailsActivity.class);
|
||||
intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
|
||||
intent.putExtra("account", contact.getAccount().getJid());
|
||||
intent.putExtra("contact", contact.getJid());
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
public void switchToAccount(Account account) {
|
||||
Intent intent = new Intent(this, EditAccountActivity.class);
|
||||
intent.putExtra("jid", account.getJid());
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
protected void inviteToConversation(Conversation conversation) {
|
||||
Intent intent = new Intent(getApplicationContext(),
|
||||
ChooseContactActivity.class);
|
||||
intent.putExtra("conversation", conversation.getUuid());
|
||||
startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION);
|
||||
}
|
||||
|
||||
protected void announcePgp(Account account, final Conversation conversation) {
|
||||
xmppConnectionService.getPgpEngine().generateSignature(account,
|
||||
"online", new UiCallback<Account>() {
|
||||
|
||||
@Override
|
||||
public void userInputRequried(PendingIntent pi,
|
||||
Account account) {
|
||||
try {
|
||||
startIntentSenderForResult(pi.getIntentSender(),
|
||||
REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
|
||||
} catch (SendIntentException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success(Account account) {
|
||||
xmppConnectionService.databaseBackend
|
||||
.updateAccount(account);
|
||||
xmppConnectionService.sendPresencePacket(account,
|
||||
xmppConnectionService.getPresenceGenerator()
|
||||
.sendPresence(account));
|
||||
if (conversation != null) {
|
||||
conversation
|
||||
.setNextEncryption(Message.ENCRYPTION_PGP);
|
||||
xmppConnectionService.databaseBackend
|
||||
.updateConversation(conversation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int error, Account account) {
|
||||
displayErrorDialog(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void displayErrorDialog(final int errorCode) {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(
|
||||
XmppActivity.this);
|
||||
builder.setIconAttribute(android.R.attr.alertDialogIcon);
|
||||
builder.setTitle(getString(R.string.error));
|
||||
builder.setMessage(errorCode);
|
||||
builder.setNeutralButton(R.string.accept, null);
|
||||
builder.create().show();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
protected void showAddToRosterDialog(final Conversation conversation) {
|
||||
String jid = conversation.getContactJid();
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(jid);
|
||||
builder.setMessage(getString(R.string.not_in_roster));
|
||||
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||
builder.setPositiveButton(getString(R.string.add_contact),
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String jid = conversation.getContactJid();
|
||||
Account account = conversation.getAccount();
|
||||
Contact contact = account.getRoster().getContact(jid);
|
||||
xmppConnectionService.createContact(contact);
|
||||
switchToContactDetails(contact);
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void showAskForPresenceDialog(final Contact contact) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(contact.getJid());
|
||||
builder.setMessage(R.string.request_presence_updates);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setPositiveButton(R.string.request_now,
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (xmppConnectionServiceBound) {
|
||||
xmppConnectionService.sendPresencePacket(contact
|
||||
.getAccount(), xmppConnectionService
|
||||
.getPresenceGenerator()
|
||||
.requestPresenceUpdatesFrom(contact));
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void warnMutalPresenceSubscription(final Conversation conversation,
|
||||
final OnPresenceSelected listener) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(conversation.getContact().getJid());
|
||||
builder.setMessage(R.string.without_mutual_presence_updates);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setPositiveButton(R.string.ignore, new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
conversation.setNextPresence(null);
|
||||
if (listener != null) {
|
||||
listener.onPresenceSelected();
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
protected void quickEdit(String previousValue, OnValueEdited callback) {
|
||||
quickEdit(previousValue, callback, false);
|
||||
}
|
||||
|
||||
protected void quickPasswordEdit(String previousValue,
|
||||
OnValueEdited callback) {
|
||||
quickEdit(previousValue, callback, true);
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
private void quickEdit(final String previousValue,
|
||||
final OnValueEdited callback, boolean password) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
View view = (View) getLayoutInflater()
|
||||
.inflate(R.layout.quickedit, null);
|
||||
final EditText editor = (EditText) view.findViewById(R.id.editor);
|
||||
OnClickListener mClickListener = new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String value = editor.getText().toString();
|
||||
if (!previousValue.equals(value) && value.trim().length() > 0) {
|
||||
callback.onValueEdited(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (password) {
|
||||
editor.setInputType(InputType.TYPE_CLASS_TEXT
|
||||
| InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
editor.setHint(R.string.password);
|
||||
builder.setPositiveButton(R.string.accept, mClickListener);
|
||||
} else {
|
||||
builder.setPositiveButton(R.string.edit, mClickListener);
|
||||
}
|
||||
editor.requestFocus();
|
||||
editor.setText(previousValue);
|
||||
builder.setView(view);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
public void selectPresence(final Conversation conversation,
|
||||
final OnPresenceSelected listener) {
|
||||
Contact contact = conversation.getContact();
|
||||
if (!contact.showInRoster()) {
|
||||
showAddToRosterDialog(conversation);
|
||||
} else {
|
||||
Presences presences = contact.getPresences();
|
||||
if (presences.size() == 0) {
|
||||
if (!contact.getOption(Contact.Options.TO)
|
||||
&& !contact.getOption(Contact.Options.ASKING)
|
||||
&& contact.getAccount().getStatus() == Account.STATUS_ONLINE) {
|
||||
showAskForPresenceDialog(contact);
|
||||
} else if (!contact.getOption(Contact.Options.TO)
|
||||
|| !contact.getOption(Contact.Options.FROM)) {
|
||||
warnMutalPresenceSubscription(conversation, listener);
|
||||
} else {
|
||||
conversation.setNextPresence(null);
|
||||
listener.onPresenceSelected();
|
||||
}
|
||||
} else if (presences.size() == 1) {
|
||||
String presence = (String) presences.asStringArray()[0];
|
||||
conversation.setNextPresence(presence);
|
||||
listener.onPresenceSelected();
|
||||
} else {
|
||||
final StringBuilder presence = new StringBuilder();
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(getString(R.string.choose_presence));
|
||||
final String[] presencesArray = presences.asStringArray();
|
||||
int preselectedPresence = 0;
|
||||
for (int i = 0; i < presencesArray.length; ++i) {
|
||||
if (presencesArray[i].equals(contact.lastseen.presence)) {
|
||||
preselectedPresence = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
presence.append(presencesArray[preselectedPresence]);
|
||||
builder.setSingleChoiceItems(presencesArray,
|
||||
preselectedPresence,
|
||||
new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
presence.delete(0, presence.length());
|
||||
presence.append(presencesArray[which]);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setPositiveButton(R.string.ok, new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
conversation.setNextPresence(presence.toString());
|
||||
listener.onPresenceSelected();
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onActivityResult(int requestCode, int resultCode,
|
||||
final Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQUEST_INVITE_TO_CONVERSATION
|
||||
&& resultCode == RESULT_OK) {
|
||||
String contactJid = data.getStringExtra("contact");
|
||||
String conversationUuid = data.getStringExtra("conversation");
|
||||
Conversation conversation = xmppConnectionService
|
||||
.findConversationByUuid(conversationUuid);
|
||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||
xmppConnectionService.invite(conversation, contactJid);
|
||||
}
|
||||
Log.d(Config.LOGTAG, "inviting " + contactJid + " to "
|
||||
+ conversation.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public int getSecondaryTextColor() {
|
||||
return this.mSecondaryTextColor;
|
||||
}
|
||||
|
||||
public int getPrimaryTextColor() {
|
||||
return this.mPrimaryTextColor;
|
||||
}
|
||||
|
||||
public int getWarningTextColor() {
|
||||
return this.mColorRed;
|
||||
}
|
||||
|
||||
public int getPrimaryColor() {
|
||||
return this.mPrimaryColor;
|
||||
}
|
||||
|
||||
public int getSecondaryBackgroundColor() {
|
||||
return this.mSecondaryBackgroundColor;
|
||||
}
|
||||
|
||||
public int getPixel(int dp) {
|
||||
DisplayMetrics metrics = getResources().getDisplayMetrics();
|
||||
return ((int) (dp * metrics.density));
|
||||
}
|
||||
|
||||
public AvatarService avatarService() {
|
||||
return xmppConnectionService.getAvatarService();
|
||||
}
|
||||
|
||||
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
|
||||
private final WeakReference<ImageView> imageViewReference;
|
||||
private Message message = null;
|
||||
|
||||
public BitmapWorkerTask(ImageView imageView) {
|
||||
imageViewReference = new WeakReference<ImageView>(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap doInBackground(Message... params) {
|
||||
message = params[0];
|
||||
try {
|
||||
return xmppConnectionService.getFileBackend().getThumbnail(
|
||||
message, (int) (metrics.density * 288), false);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Bitmap bitmap) {
|
||||
if (imageViewReference != null && bitmap != null) {
|
||||
final ImageView imageView = imageViewReference.get();
|
||||
if (imageView != null) {
|
||||
imageView.setImageBitmap(bitmap);
|
||||
imageView.setBackgroundColor(0x00000000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadBitmap(Message message, ImageView imageView) {
|
||||
Bitmap bm;
|
||||
try {
|
||||
bm = xmppConnectionService.getFileBackend().getThumbnail(message,
|
||||
(int) (metrics.density * 288), true);
|
||||
} catch (FileNotFoundException e) {
|
||||
bm = null;
|
||||
}
|
||||
if (bm != null) {
|
||||
imageView.setImageBitmap(bm);
|
||||
imageView.setBackgroundColor(0x00000000);
|
||||
} else {
|
||||
if (cancelPotentialWork(message, imageView)) {
|
||||
imageView.setBackgroundColor(0xff333333);
|
||||
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
|
||||
final AsyncDrawable asyncDrawable = new AsyncDrawable(
|
||||
getResources(), null, task);
|
||||
imageView.setImageDrawable(asyncDrawable);
|
||||
try {
|
||||
task.execute(message);
|
||||
} catch (RejectedExecutionException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean cancelPotentialWork(Message message,
|
||||
ImageView imageView) {
|
||||
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
|
||||
|
||||
if (bitmapWorkerTask != null) {
|
||||
final Message oldMessage = bitmapWorkerTask.message;
|
||||
if (oldMessage == null || message != oldMessage) {
|
||||
bitmapWorkerTask.cancel(true);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
|
||||
if (imageView != null) {
|
||||
final Drawable drawable = imageView.getDrawable();
|
||||
if (drawable instanceof AsyncDrawable) {
|
||||
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
|
||||
return asyncDrawable.getBitmapWorkerTask();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static class AsyncDrawable extends BitmapDrawable {
|
||||
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
|
||||
|
||||
public AsyncDrawable(Resources res, Bitmap bitmap,
|
||||
BitmapWorkerTask bitmapWorkerTask) {
|
||||
super(res, bitmap);
|
||||
bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(
|
||||
bitmapWorkerTask);
|
||||
}
|
||||
|
||||
public BitmapWorkerTask getBitmapWorkerTask() {
|
||||
return bitmapWorkerTaskReference.get();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package eu.siacs.conversations.ui.adapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.ui.XmppActivity;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class AccountAdapter extends ArrayAdapter<Account> {
|
||||
|
||||
private XmppActivity activity;
|
||||
|
||||
public AccountAdapter(XmppActivity activity, List<Account> objects) {
|
||||
super(activity, 0, objects);
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
Account account = getItem(position);
|
||||
if (view == null) {
|
||||
LayoutInflater inflater = (LayoutInflater) getContext()
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = (View) inflater.inflate(R.layout.account_row, parent, false);
|
||||
}
|
||||
TextView jid = (TextView) view.findViewById(R.id.account_jid);
|
||||
jid.setText(account.getJid());
|
||||
TextView statusView = (TextView) view.findViewById(R.id.account_status);
|
||||
ImageView imageView = (ImageView) view.findViewById(R.id.account_image);
|
||||
imageView.setImageBitmap(activity.avatarService().get(account,
|
||||
activity.getPixel(48)));
|
||||
switch (account.getStatus()) {
|
||||
case Account.STATUS_DISABLED:
|
||||
statusView.setText(getContext().getString(
|
||||
R.string.account_status_disabled));
|
||||
statusView.setTextColor(activity.getSecondaryTextColor());
|
||||
break;
|
||||
case Account.STATUS_ONLINE:
|
||||
statusView.setText(getContext().getString(
|
||||
R.string.account_status_online));
|
||||
statusView.setTextColor(activity.getPrimaryColor());
|
||||
break;
|
||||
case Account.STATUS_CONNECTING:
|
||||
statusView.setText(getContext().getString(
|
||||
R.string.account_status_connecting));
|
||||
statusView.setTextColor(activity.getSecondaryTextColor());
|
||||
break;
|
||||
case Account.STATUS_OFFLINE:
|
||||
statusView.setText(getContext().getString(
|
||||
R.string.account_status_offline));
|
||||
statusView.setTextColor(activity.getWarningTextColor());
|
||||
break;
|
||||
case Account.STATUS_UNAUTHORIZED:
|
||||
statusView.setText(getContext().getString(
|
||||
R.string.account_status_unauthorized));
|
||||
statusView.setTextColor(activity.getWarningTextColor());
|
||||
break;
|
||||
case Account.STATUS_SERVER_NOT_FOUND:
|
||||
statusView.setText(getContext().getString(
|
||||
R.string.account_status_not_found));
|
||||
statusView.setTextColor(activity.getWarningTextColor());
|
||||
break;
|
||||
case Account.STATUS_NO_INTERNET:
|
||||
statusView.setText(getContext().getString(
|
||||
R.string.account_status_no_internet));
|
||||
statusView.setTextColor(activity.getWarningTextColor());
|
||||
break;
|
||||
case Account.STATUS_REGISTRATION_FAILED:
|
||||
statusView.setText(getContext().getString(
|
||||
R.string.account_status_regis_fail));
|
||||
statusView.setTextColor(activity.getWarningTextColor());
|
||||
break;
|
||||
case Account.STATUS_REGISTRATION_CONFLICT:
|
||||
statusView.setText(getContext().getString(
|
||||
R.string.account_status_regis_conflict));
|
||||
statusView.setTextColor(activity.getWarningTextColor());
|
||||
break;
|
||||
case Account.STATUS_REGISTRATION_SUCCESSFULL:
|
||||
statusView.setText(getContext().getString(
|
||||
R.string.account_status_regis_success));
|
||||
statusView.setTextColor(activity.getSecondaryTextColor());
|
||||
break;
|
||||
case Account.STATUS_REGISTRATION_NOT_SUPPORTED:
|
||||
statusView.setText(getContext().getString(
|
||||
R.string.account_status_regis_not_sup));
|
||||
statusView.setTextColor(activity.getWarningTextColor());
|
||||
break;
|
||||
default:
|
||||
statusView.setText("");
|
||||
break;
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package eu.siacs.conversations.ui.adapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Downloadable;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.ui.ConversationActivity;
|
||||
import eu.siacs.conversations.ui.XmppActivity;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class ConversationAdapter extends ArrayAdapter<Conversation> {
|
||||
|
||||
private XmppActivity activity;
|
||||
|
||||
public ConversationAdapter(XmppActivity activity,
|
||||
List<Conversation> conversations) {
|
||||
super(activity, 0, conversations);
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
if (view == null) {
|
||||
LayoutInflater inflater = (LayoutInflater) activity
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = (View) inflater.inflate(R.layout.conversation_list_row,
|
||||
parent, false);
|
||||
}
|
||||
Conversation conversation = getItem(position);
|
||||
if (this.activity instanceof ConversationActivity) {
|
||||
ConversationActivity activity = (ConversationActivity) this.activity;
|
||||
if (!activity.isConversationsOverviewHideable()) {
|
||||
if (conversation == activity.getSelectedConversation()) {
|
||||
view.setBackgroundColor(activity
|
||||
.getSecondaryBackgroundColor());
|
||||
} else {
|
||||
view.setBackgroundColor(Color.TRANSPARENT);
|
||||
}
|
||||
} else {
|
||||
view.setBackgroundColor(Color.TRANSPARENT);
|
||||
}
|
||||
}
|
||||
TextView convName = (TextView) view
|
||||
.findViewById(R.id.conversation_name);
|
||||
if (conversation.getMode() == Conversation.MODE_SINGLE
|
||||
|| activity.useSubjectToIdentifyConference()) {
|
||||
convName.setText(conversation.getName());
|
||||
} else {
|
||||
convName.setText(conversation.getContactJid().split("/")[0]);
|
||||
}
|
||||
TextView mLastMessage = (TextView) view
|
||||
.findViewById(R.id.conversation_lastmsg);
|
||||
TextView mTimestamp = (TextView) view
|
||||
.findViewById(R.id.conversation_lastupdate);
|
||||
ImageView imagePreview = (ImageView) view
|
||||
.findViewById(R.id.conversation_lastimage);
|
||||
|
||||
Message message = conversation.getLatestMessage();
|
||||
|
||||
if (!conversation.isRead()) {
|
||||
convName.setTypeface(null, Typeface.BOLD);
|
||||
} else {
|
||||
convName.setTypeface(null, Typeface.NORMAL);
|
||||
}
|
||||
|
||||
if (message.getType() == Message.TYPE_IMAGE
|
||||
|| message.getDownloadable() != null) {
|
||||
Downloadable d = message.getDownloadable();
|
||||
if (d != null) {
|
||||
mLastMessage.setVisibility(View.VISIBLE);
|
||||
imagePreview.setVisibility(View.GONE);
|
||||
if (conversation.isRead()) {
|
||||
mLastMessage.setTypeface(null, Typeface.ITALIC);
|
||||
} else {
|
||||
mLastMessage.setTypeface(null, Typeface.BOLD_ITALIC);
|
||||
}
|
||||
if (d.getStatus() == Downloadable.STATUS_CHECKING) {
|
||||
mLastMessage.setText(R.string.checking_image);
|
||||
} else if (d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
|
||||
mLastMessage.setText(R.string.receiving_image);
|
||||
} else if (d.getStatus() == Downloadable.STATUS_OFFER) {
|
||||
mLastMessage.setText(R.string.image_offered_for_download);
|
||||
} else if (d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
|
||||
mLastMessage.setText(R.string.image_offered_for_download);
|
||||
} else if (d.getStatus() == Downloadable.STATUS_DELETED) {
|
||||
mLastMessage.setText(R.string.image_file_deleted);
|
||||
} else {
|
||||
mLastMessage.setText("");
|
||||
}
|
||||
} else {
|
||||
mLastMessage.setVisibility(View.GONE);
|
||||
imagePreview.setVisibility(View.VISIBLE);
|
||||
activity.loadBitmap(message, imagePreview);
|
||||
}
|
||||
} else {
|
||||
if ((message.getEncryption() != Message.ENCRYPTION_PGP)
|
||||
&& (message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)) {
|
||||
String body = Config.PARSE_EMOTICONS ? UIHelper
|
||||
.transformAsciiEmoticons(message.getBody()) : message
|
||||
.getBody();
|
||||
mLastMessage.setText(body);
|
||||
} else {
|
||||
mLastMessage.setText(R.string.encrypted_message_received);
|
||||
}
|
||||
if (!conversation.isRead()) {
|
||||
mLastMessage.setTypeface(null, Typeface.BOLD);
|
||||
} else {
|
||||
mLastMessage.setTypeface(null, Typeface.NORMAL);
|
||||
}
|
||||
mLastMessage.setVisibility(View.VISIBLE);
|
||||
imagePreview.setVisibility(View.GONE);
|
||||
}
|
||||
mTimestamp.setText(UIHelper.readableTimeDifference(getContext(),
|
||||
conversation.getLatestMessage().getTimeSent()));
|
||||
|
||||
ImageView profilePicture = (ImageView) view
|
||||
.findViewById(R.id.conversation_image);
|
||||
profilePicture.setImageBitmap(activity.avatarService().get(
|
||||
conversation, activity.getPixel(56)));
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package eu.siacs.conversations.ui.adapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Filter;
|
||||
|
||||
public class KnownHostsAdapter extends ArrayAdapter<String> {
|
||||
private ArrayList<String> domains;
|
||||
private Filter domainFilter = new Filter() {
|
||||
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
if (constraint != null) {
|
||||
ArrayList<String> suggestions = new ArrayList<String>();
|
||||
final String[] split = constraint.toString().split("@");
|
||||
if (split.length == 1) {
|
||||
for (String domain : domains) {
|
||||
suggestions.add(split[0].toLowerCase(Locale
|
||||
.getDefault()) + "@" + domain);
|
||||
}
|
||||
} else if (split.length == 2) {
|
||||
for (String domain : domains) {
|
||||
if (domain.contentEquals(split[1])) {
|
||||
suggestions.clear();
|
||||
break;
|
||||
} else if (domain.contains(split[1])) {
|
||||
suggestions.add(split[0].toLowerCase(Locale
|
||||
.getDefault()) + "@" + domain);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return new FilterResults();
|
||||
}
|
||||
FilterResults filterResults = new FilterResults();
|
||||
filterResults.values = suggestions;
|
||||
filterResults.count = suggestions.size();
|
||||
return filterResults;
|
||||
} else {
|
||||
return new FilterResults();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint,
|
||||
FilterResults results) {
|
||||
ArrayList filteredList = (ArrayList) results.values;
|
||||
if (results != null && results.count > 0) {
|
||||
clear();
|
||||
for (Object c : filteredList) {
|
||||
add((String) c);
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public KnownHostsAdapter(Context context, int viewResourceId,
|
||||
List<String> mKnownHosts) {
|
||||
super(context, viewResourceId, mKnownHosts);
|
||||
domains = new ArrayList<String>(mKnownHosts.size());
|
||||
for (String domain : mKnownHosts) {
|
||||
domains.add(new String(domain));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return domainFilter;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package eu.siacs.conversations.ui.adapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.ListItem;
|
||||
import eu.siacs.conversations.ui.XmppActivity;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class ListItemAdapter extends ArrayAdapter<ListItem> {
|
||||
|
||||
protected XmppActivity activity;
|
||||
|
||||
public ListItemAdapter(XmppActivity activity, List<ListItem> objects) {
|
||||
super(activity, 0, objects);
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
LayoutInflater inflater = (LayoutInflater) getContext()
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
ListItem item = getItem(position);
|
||||
if (view == null) {
|
||||
view = (View) inflater.inflate(R.layout.contact, parent, false);
|
||||
}
|
||||
TextView name = (TextView) view.findViewById(R.id.contact_display_name);
|
||||
TextView jid = (TextView) view.findViewById(R.id.contact_jid);
|
||||
ImageView picture = (ImageView) view.findViewById(R.id.contact_photo);
|
||||
|
||||
jid.setText(item.getJid());
|
||||
name.setText(item.getDisplayName());
|
||||
picture.setImageBitmap(activity.avatarService().get(item,
|
||||
activity.getPixel(48)));
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,560 @@
|
||||
package eu.siacs.conversations.ui.adapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Downloadable;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.entities.Message.ImageParams;
|
||||
import eu.siacs.conversations.ui.ConversationActivity;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnLongClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class MessageAdapter extends ArrayAdapter<Message> {
|
||||
|
||||
private static final int SENT = 0;
|
||||
private static final int RECEIVED = 1;
|
||||
private static final int STATUS = 2;
|
||||
private static final int NULL = 3;
|
||||
|
||||
private ConversationActivity activity;
|
||||
|
||||
private DisplayMetrics metrics;
|
||||
|
||||
private OnContactPictureClicked mOnContactPictureClickedListener;
|
||||
private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
|
||||
|
||||
public MessageAdapter(ConversationActivity activity, List<Message> messages) {
|
||||
super(activity, 0, messages);
|
||||
this.activity = activity;
|
||||
metrics = getContext().getResources().getDisplayMetrics();
|
||||
}
|
||||
|
||||
public void setOnContactPictureClicked(OnContactPictureClicked listener) {
|
||||
this.mOnContactPictureClickedListener = listener;
|
||||
}
|
||||
|
||||
public void setOnContactPictureLongClicked(
|
||||
OnContactPictureLongClicked listener) {
|
||||
this.mOnContactPictureLongClickedListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (getItem(position).wasMergedIntoPrevious()) {
|
||||
return NULL;
|
||||
} else if (getItem(position).getType() == Message.TYPE_STATUS) {
|
||||
return STATUS;
|
||||
} else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) {
|
||||
return RECEIVED;
|
||||
} else {
|
||||
return SENT;
|
||||
}
|
||||
}
|
||||
|
||||
private void displayStatus(ViewHolder viewHolder, Message message) {
|
||||
String filesize = null;
|
||||
String info = null;
|
||||
boolean error = false;
|
||||
if (viewHolder.indicatorReceived != null) {
|
||||
viewHolder.indicatorReceived.setVisibility(View.GONE);
|
||||
}
|
||||
boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
|
||||
&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
|
||||
if (message.getType() == Message.TYPE_IMAGE
|
||||
|| message.getDownloadable() != null) {
|
||||
ImageParams params = message.getImageParams();
|
||||
if (params.size != 0) {
|
||||
filesize = params.size / 1024 + " KB";
|
||||
}
|
||||
}
|
||||
switch (message.getMergedStatus()) {
|
||||
case Message.STATUS_WAITING:
|
||||
info = getContext().getString(R.string.waiting);
|
||||
break;
|
||||
case Message.STATUS_UNSEND:
|
||||
info = getContext().getString(R.string.sending);
|
||||
break;
|
||||
case Message.STATUS_OFFERED:
|
||||
info = getContext().getString(R.string.offering);
|
||||
break;
|
||||
case Message.STATUS_SEND_RECEIVED:
|
||||
if (activity.indicateReceived()) {
|
||||
viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
|
||||
}
|
||||
break;
|
||||
case Message.STATUS_SEND_DISPLAYED:
|
||||
if (activity.indicateReceived()) {
|
||||
viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
|
||||
}
|
||||
break;
|
||||
case Message.STATUS_SEND_FAILED:
|
||||
info = getContext().getString(R.string.send_failed);
|
||||
error = true;
|
||||
break;
|
||||
case Message.STATUS_SEND_REJECTED:
|
||||
info = getContext().getString(R.string.send_rejected);
|
||||
error = true;
|
||||
break;
|
||||
default:
|
||||
if (multiReceived) {
|
||||
Contact contact = message.getContact();
|
||||
if (contact != null) {
|
||||
info = contact.getDisplayName();
|
||||
} else {
|
||||
if (message.getPresence() != null) {
|
||||
info = message.getPresence();
|
||||
} else {
|
||||
info = message.getCounterpart();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (error) {
|
||||
viewHolder.time.setTextColor(activity.getWarningTextColor());
|
||||
} else {
|
||||
viewHolder.time.setTextColor(activity.getSecondaryTextColor());
|
||||
}
|
||||
if (message.getEncryption() == Message.ENCRYPTION_NONE) {
|
||||
viewHolder.indicator.setVisibility(View.GONE);
|
||||
} else {
|
||||
viewHolder.indicator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(),
|
||||
message.getMergedTimeSent());
|
||||
if (message.getStatus() <= Message.STATUS_RECEIVED) {
|
||||
if ((filesize != null) && (info != null)) {
|
||||
viewHolder.time.setText(filesize + " \u00B7 " + info);
|
||||
} else if ((filesize == null) && (info != null)) {
|
||||
viewHolder.time.setText(formatedTime + " \u00B7 " + info);
|
||||
} else if ((filesize != null) && (info == null)) {
|
||||
viewHolder.time.setText(formatedTime + " \u00B7 " + filesize);
|
||||
} else {
|
||||
viewHolder.time.setText(formatedTime);
|
||||
}
|
||||
} else {
|
||||
if ((filesize != null) && (info != null)) {
|
||||
viewHolder.time.setText(filesize + " \u00B7 " + info);
|
||||
} else if ((filesize == null) && (info != null)) {
|
||||
if (error) {
|
||||
viewHolder.time.setText(info + " \u00B7 " + formatedTime);
|
||||
} else {
|
||||
viewHolder.time.setText(info);
|
||||
}
|
||||
} else if ((filesize != null) && (info == null)) {
|
||||
viewHolder.time.setText(filesize + " \u00B7 " + formatedTime);
|
||||
} else {
|
||||
viewHolder.time.setText(formatedTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void displayInfoMessage(ViewHolder viewHolder, int r) {
|
||||
if (viewHolder.download_button != null) {
|
||||
viewHolder.download_button.setVisibility(View.GONE);
|
||||
}
|
||||
viewHolder.image.setVisibility(View.GONE);
|
||||
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
||||
viewHolder.messageBody.setText(getContext().getString(r));
|
||||
viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
|
||||
viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
|
||||
viewHolder.messageBody.setTextIsSelectable(false);
|
||||
}
|
||||
|
||||
private void displayDecryptionFailed(ViewHolder viewHolder) {
|
||||
if (viewHolder.download_button != null) {
|
||||
viewHolder.download_button.setVisibility(View.GONE);
|
||||
}
|
||||
viewHolder.image.setVisibility(View.GONE);
|
||||
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
||||
viewHolder.messageBody.setText(getContext().getString(
|
||||
R.string.decryption_failed));
|
||||
viewHolder.messageBody.setTextColor(activity.getWarningTextColor());
|
||||
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
|
||||
viewHolder.messageBody.setTextIsSelectable(false);
|
||||
}
|
||||
|
||||
private void displayTextMessage(ViewHolder viewHolder, Message message) {
|
||||
if (viewHolder.download_button != null) {
|
||||
viewHolder.download_button.setVisibility(View.GONE);
|
||||
}
|
||||
viewHolder.image.setVisibility(View.GONE);
|
||||
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
||||
if (message.getBody() != null) {
|
||||
if (message.getType() != Message.TYPE_PRIVATE) {
|
||||
String body = Config.PARSE_EMOTICONS ? UIHelper
|
||||
.transformAsciiEmoticons(message.getMergedBody())
|
||||
: message.getMergedBody();
|
||||
viewHolder.messageBody.setText(body);
|
||||
} else {
|
||||
String privateMarker;
|
||||
if (message.getStatus() <= Message.STATUS_RECEIVED) {
|
||||
privateMarker = activity
|
||||
.getString(R.string.private_message);
|
||||
} else {
|
||||
String to;
|
||||
if (message.getPresence() != null) {
|
||||
to = message.getPresence();
|
||||
} else {
|
||||
to = message.getCounterpart();
|
||||
}
|
||||
privateMarker = activity.getString(
|
||||
R.string.private_message_to, to);
|
||||
}
|
||||
SpannableString span = new SpannableString(privateMarker + " "
|
||||
+ message.getBody());
|
||||
span.setSpan(
|
||||
new ForegroundColorSpan(activity
|
||||
.getSecondaryTextColor()), 0, privateMarker
|
||||
.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
span.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0,
|
||||
privateMarker.length(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
viewHolder.messageBody.setText(span);
|
||||
}
|
||||
} else {
|
||||
viewHolder.messageBody.setText("");
|
||||
}
|
||||
viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor());
|
||||
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
|
||||
viewHolder.messageBody.setTextIsSelectable(true);
|
||||
}
|
||||
|
||||
private void displayDownloadableMessage(ViewHolder viewHolder,
|
||||
final Message message, int resid) {
|
||||
viewHolder.image.setVisibility(View.GONE);
|
||||
viewHolder.messageBody.setVisibility(View.GONE);
|
||||
viewHolder.download_button.setVisibility(View.VISIBLE);
|
||||
viewHolder.download_button.setText(resid);
|
||||
viewHolder.download_button.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startDonwloadable(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayImageMessage(ViewHolder viewHolder,
|
||||
final Message message) {
|
||||
if (viewHolder.download_button != null) {
|
||||
viewHolder.download_button.setVisibility(View.GONE);
|
||||
}
|
||||
viewHolder.messageBody.setVisibility(View.GONE);
|
||||
viewHolder.image.setVisibility(View.VISIBLE);
|
||||
ImageParams params = message.getImageParams();
|
||||
double target = metrics.density * 288;
|
||||
int scalledW;
|
||||
int scalledH;
|
||||
if (params.width <= params.height) {
|
||||
scalledW = (int) (params.width / ((double) params.height / target));
|
||||
scalledH = (int) target;
|
||||
} else {
|
||||
scalledW = (int) target;
|
||||
scalledH = (int) (params.height / ((double) params.width / target));
|
||||
}
|
||||
viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
scalledW, scalledH));
|
||||
activity.loadBitmap(message, viewHolder.image);
|
||||
viewHolder.image.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(activity.xmppConnectionService
|
||||
.getFileBackend().getJingleFileUri(message), "image/*");
|
||||
getContext().startActivity(intent);
|
||||
}
|
||||
});
|
||||
viewHolder.image.setOnLongClickListener(new OnLongClickListener() {
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
Intent shareIntent = new Intent();
|
||||
shareIntent.setAction(Intent.ACTION_SEND);
|
||||
shareIntent.putExtra(Intent.EXTRA_STREAM,
|
||||
activity.xmppConnectionService.getFileBackend()
|
||||
.getJingleFileUri(message));
|
||||
shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
shareIntent.setType("image/webp");
|
||||
getContext().startActivity(
|
||||
Intent.createChooser(shareIntent,
|
||||
getContext().getText(R.string.share_with)));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
final Message item = getItem(position);
|
||||
int type = getItemViewType(position);
|
||||
ViewHolder viewHolder;
|
||||
if (view == null) {
|
||||
viewHolder = new ViewHolder();
|
||||
switch (type) {
|
||||
case NULL:
|
||||
view = (View) activity.getLayoutInflater().inflate(
|
||||
R.layout.message_null, parent, false);
|
||||
break;
|
||||
case SENT:
|
||||
view = (View) activity.getLayoutInflater().inflate(
|
||||
R.layout.message_sent, parent, false);
|
||||
viewHolder.message_box = (LinearLayout) view
|
||||
.findViewById(R.id.message_box);
|
||||
viewHolder.contact_picture = (ImageView) view
|
||||
.findViewById(R.id.message_photo);
|
||||
viewHolder.contact_picture.setImageBitmap(activity
|
||||
.avatarService().get(
|
||||
item.getConversation().getAccount(),
|
||||
activity.getPixel(48)));
|
||||
viewHolder.download_button = (Button) view
|
||||
.findViewById(R.id.download_button);
|
||||
viewHolder.indicator = (ImageView) view
|
||||
.findViewById(R.id.security_indicator);
|
||||
viewHolder.image = (ImageView) view
|
||||
.findViewById(R.id.message_image);
|
||||
viewHolder.messageBody = (TextView) view
|
||||
.findViewById(R.id.message_body);
|
||||
viewHolder.time = (TextView) view
|
||||
.findViewById(R.id.message_time);
|
||||
viewHolder.indicatorReceived = (ImageView) view
|
||||
.findViewById(R.id.indicator_received);
|
||||
view.setTag(viewHolder);
|
||||
break;
|
||||
case RECEIVED:
|
||||
view = (View) activity.getLayoutInflater().inflate(
|
||||
R.layout.message_received, parent, false);
|
||||
viewHolder.message_box = (LinearLayout) view
|
||||
.findViewById(R.id.message_box);
|
||||
viewHolder.contact_picture = (ImageView) view
|
||||
.findViewById(R.id.message_photo);
|
||||
viewHolder.download_button = (Button) view
|
||||
.findViewById(R.id.download_button);
|
||||
if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
|
||||
viewHolder.contact_picture.setImageBitmap(activity
|
||||
.avatarService().get(item.getContact(),
|
||||
activity.getPixel(48)));
|
||||
}
|
||||
viewHolder.indicator = (ImageView) view
|
||||
.findViewById(R.id.security_indicator);
|
||||
viewHolder.image = (ImageView) view
|
||||
.findViewById(R.id.message_image);
|
||||
viewHolder.messageBody = (TextView) view
|
||||
.findViewById(R.id.message_body);
|
||||
viewHolder.time = (TextView) view
|
||||
.findViewById(R.id.message_time);
|
||||
view.setTag(viewHolder);
|
||||
break;
|
||||
case STATUS:
|
||||
view = (View) activity.getLayoutInflater().inflate(
|
||||
R.layout.message_status, parent, false);
|
||||
viewHolder.contact_picture = (ImageView) view
|
||||
.findViewById(R.id.message_photo);
|
||||
if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
|
||||
|
||||
viewHolder.contact_picture.setImageBitmap(activity
|
||||
.avatarService().get(
|
||||
item.getConversation().getContact(),
|
||||
activity.getPixel(32)));
|
||||
viewHolder.contact_picture.setAlpha(0.5f);
|
||||
viewHolder.contact_picture
|
||||
.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
String name = item.getConversation()
|
||||
.getName();
|
||||
String read = getContext()
|
||||
.getString(
|
||||
R.string.contact_has_read_up_to_this_point,
|
||||
name);
|
||||
Toast.makeText(getContext(), read,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
viewHolder = null;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
viewHolder = (ViewHolder) view.getTag();
|
||||
}
|
||||
|
||||
if (type == STATUS) {
|
||||
return view;
|
||||
}
|
||||
if (type == NULL) {
|
||||
if (position == getCount() - 1) {
|
||||
view.getLayoutParams().height = 1;
|
||||
} else {
|
||||
view.getLayoutParams().height = 0;
|
||||
|
||||
}
|
||||
view.setLayoutParams(view.getLayoutParams());
|
||||
return view;
|
||||
}
|
||||
|
||||
if (viewHolder.contact_picture != null) {
|
||||
viewHolder.contact_picture
|
||||
.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
|
||||
MessageAdapter.this.mOnContactPictureClickedListener
|
||||
.onContactPictureClicked(item);
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
viewHolder.contact_picture
|
||||
.setOnLongClickListener(new OnLongClickListener() {
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
|
||||
MessageAdapter.this.mOnContactPictureLongClickedListener
|
||||
.onContactPictureLongClicked(item);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (type == RECEIVED) {
|
||||
if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
|
||||
Contact contact = item.getContact();
|
||||
if (contact != null) {
|
||||
viewHolder.contact_picture.setImageBitmap(activity
|
||||
.avatarService()
|
||||
.get(contact, activity.getPixel(48)));
|
||||
} else {
|
||||
String name = item.getPresence();
|
||||
if (name == null) {
|
||||
name = item.getCounterpart();
|
||||
}
|
||||
viewHolder.contact_picture.setImageBitmap(activity
|
||||
.avatarService().get(name, activity.getPixel(48)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.getType() == Message.TYPE_IMAGE
|
||||
|| item.getDownloadable() != null) {
|
||||
Downloadable d = item.getDownloadable();
|
||||
if (d != null && d.getStatus() == Downloadable.STATUS_DOWNLOADING) {
|
||||
displayInfoMessage(viewHolder, R.string.receiving_image);
|
||||
} else if (d != null
|
||||
&& d.getStatus() == Downloadable.STATUS_CHECKING) {
|
||||
displayInfoMessage(viewHolder, R.string.checking_image);
|
||||
} else if (d != null
|
||||
&& d.getStatus() == Downloadable.STATUS_DELETED) {
|
||||
displayInfoMessage(viewHolder, R.string.image_file_deleted);
|
||||
} else if (d != null && d.getStatus() == Downloadable.STATUS_OFFER) {
|
||||
displayDownloadableMessage(viewHolder, item,
|
||||
R.string.download_image);
|
||||
} else if (d != null
|
||||
&& d.getStatus() == Downloadable.STATUS_OFFER_CHECK_FILESIZE) {
|
||||
displayDownloadableMessage(viewHolder, item,
|
||||
R.string.check_image_filesize);
|
||||
} else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
|
||||
|| (item.getEncryption() == Message.ENCRYPTION_NONE)
|
||||
|| (item.getEncryption() == Message.ENCRYPTION_OTR)) {
|
||||
displayImageMessage(viewHolder, item);
|
||||
} else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||
displayInfoMessage(viewHolder, R.string.encrypted_message);
|
||||
} else {
|
||||
displayDecryptionFailed(viewHolder);
|
||||
}
|
||||
} else {
|
||||
if (item.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||
if (activity.hasPgp()) {
|
||||
displayInfoMessage(viewHolder, R.string.encrypted_message);
|
||||
} else {
|
||||
displayInfoMessage(viewHolder,
|
||||
R.string.install_openkeychain);
|
||||
viewHolder.message_box
|
||||
.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
activity.showInstallPgpDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
|
||||
displayDecryptionFailed(viewHolder);
|
||||
} else {
|
||||
displayTextMessage(viewHolder, item);
|
||||
}
|
||||
}
|
||||
|
||||
displayStatus(viewHolder, item);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public void startDonwloadable(Message message) {
|
||||
Downloadable downloadable = message.getDownloadable();
|
||||
if (downloadable != null) {
|
||||
if (!downloadable.start()) {
|
||||
Toast.makeText(activity, R.string.not_connected_try_again,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
|
||||
protected LinearLayout message_box;
|
||||
protected Button download_button;
|
||||
protected ImageView image;
|
||||
protected ImageView indicator;
|
||||
protected ImageView indicatorReceived;
|
||||
protected TextView time;
|
||||
protected TextView messageBody;
|
||||
protected ImageView contact_picture;
|
||||
|
||||
}
|
||||
|
||||
public interface OnContactPictureClicked {
|
||||
public void onContactPictureClicked(Message message);
|
||||
}
|
||||
|
||||
public interface OnContactPictureLongClicked {
|
||||
public void onContactPictureLongClicked(Message message);
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import android.util.Base64;
|
||||
|
||||
public class CryptoHelper {
|
||||
public static final String FILETRANSFER = "?FILETRANSFERv1:";
|
||||
final protected static char[] hexArray = "0123456789abcdef".toCharArray();
|
||||
final protected static char[] vowels = "aeiou".toCharArray();
|
||||
final protected static char[] consonants = "bcdfghjklmnpqrstvwxyz"
|
||||
.toCharArray();
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = hexArray[v >>> 4];
|
||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
public static byte[] hexToBytes(String hexString) {
|
||||
int len = hexString.length();
|
||||
byte[] array = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
array[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character
|
||||
.digit(hexString.charAt(i + 1), 16));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public static String saslPlain(String username, String password) {
|
||||
String sasl = '\u0000' + username + '\u0000' + password;
|
||||
return Base64.encodeToString(sasl.getBytes(Charset.defaultCharset()),
|
||||
Base64.NO_WRAP);
|
||||
}
|
||||
|
||||
private static byte[] concatenateByteArrays(byte[] a, byte[] b) {
|
||||
byte[] result = new byte[a.length + b.length];
|
||||
System.arraycopy(a, 0, result, 0, a.length);
|
||||
System.arraycopy(b, 0, result, a.length, b.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String saslDigestMd5(Account account, String challenge,
|
||||
SecureRandom random) {
|
||||
try {
|
||||
String[] challengeParts = new String(Base64.decode(challenge,
|
||||
Base64.DEFAULT)).split(",");
|
||||
String nonce = "";
|
||||
for (int i = 0; i < challengeParts.length; ++i) {
|
||||
String[] parts = challengeParts[i].split("=");
|
||||
if (parts[0].equals("nonce")) {
|
||||
nonce = parts[1].replace("\"", "");
|
||||
} else if (parts[0].equals("rspauth")) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
String digestUri = "xmpp/" + account.getServer();
|
||||
String nonceCount = "00000001";
|
||||
String x = account.getUsername() + ":" + account.getServer() + ":"
|
||||
+ account.getPassword();
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
|
||||
String cNonce = new BigInteger(100, random).toString(32);
|
||||
byte[] a1 = concatenateByteArrays(y,
|
||||
(":" + nonce + ":" + cNonce).getBytes(Charset
|
||||
.defaultCharset()));
|
||||
String a2 = "AUTHENTICATE:" + digestUri;
|
||||
String ha1 = bytesToHex(md.digest(a1));
|
||||
String ha2 = bytesToHex(md.digest(a2.getBytes(Charset
|
||||
.defaultCharset())));
|
||||
String kd = ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce
|
||||
+ ":auth:" + ha2;
|
||||
String response = bytesToHex(md.digest(kd.getBytes(Charset
|
||||
.defaultCharset())));
|
||||
String saslString = "username=\"" + account.getUsername()
|
||||
+ "\",realm=\"" + account.getServer() + "\",nonce=\""
|
||||
+ nonce + "\",cnonce=\"" + cNonce + "\",nc=" + nonceCount
|
||||
+ ",qop=auth,digest-uri=\"" + digestUri + "\",response="
|
||||
+ response + ",charset=utf-8";
|
||||
return Base64.encodeToString(
|
||||
saslString.getBytes(Charset.defaultCharset()),
|
||||
Base64.NO_WRAP);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String randomMucName(SecureRandom random) {
|
||||
return randomWord(3, random) + "." + randomWord(7, random);
|
||||
}
|
||||
|
||||
protected static String randomWord(int lenght, SecureRandom random) {
|
||||
StringBuilder builder = new StringBuilder(lenght);
|
||||
for (int i = 0; i < lenght; ++i) {
|
||||
if (i % 2 == 0) {
|
||||
builder.append(consonants[random.nextInt(consonants.length)]);
|
||||
} else {
|
||||
builder.append(vowels[random.nextInt(vowels.length)]);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import de.measite.minidns.Client;
|
||||
import de.measite.minidns.DNSMessage;
|
||||
import de.measite.minidns.Record;
|
||||
import de.measite.minidns.Record.TYPE;
|
||||
import de.measite.minidns.Record.CLASS;
|
||||
import de.measite.minidns.record.SRV;
|
||||
import de.measite.minidns.record.A;
|
||||
import de.measite.minidns.record.AAAA;
|
||||
import de.measite.minidns.record.Data;
|
||||
import de.measite.minidns.util.NameUtil;
|
||||
import eu.siacs.conversations.Config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Random;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
public class DNSHelper {
|
||||
protected static Client client = new Client();
|
||||
|
||||
public static Bundle getSRVRecord(String host) throws IOException {
|
||||
String dns[] = client.findDNS();
|
||||
|
||||
if (dns != null) {
|
||||
for (String dnsserver : dns) {
|
||||
InetAddress ip = InetAddress.getByName(dnsserver);
|
||||
Bundle b = queryDNS(host, ip);
|
||||
if (b.containsKey("name")) {
|
||||
return b;
|
||||
} else if (b.containsKey("error")
|
||||
&& "nosrv".equals(b.getString("error", null))) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
}
|
||||
return queryDNS(host, InetAddress.getByName("8.8.8.8"));
|
||||
}
|
||||
|
||||
public static Bundle queryDNS(String host, InetAddress dnsServer) {
|
||||
Bundle namePort = new Bundle();
|
||||
try {
|
||||
String qname = "_xmpp-client._tcp." + host;
|
||||
Log.d(Config.LOGTAG,
|
||||
"using dns server: " + dnsServer.getHostAddress()
|
||||
+ " to look up " + host);
|
||||
DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN,
|
||||
dnsServer.getHostAddress());
|
||||
|
||||
// How should we handle priorities and weight?
|
||||
// Wikipedia has a nice article about priorities vs. weights:
|
||||
// https://en.wikipedia.org/wiki/SRV_record#Provisioning_for_high_service_availability
|
||||
|
||||
// we bucket the SRV records based on priority, pick per priority
|
||||
// a random order respecting the weight, and dump that priority by
|
||||
// priority
|
||||
|
||||
TreeMap<Integer, ArrayList<SRV>> priorities = new TreeMap<Integer, ArrayList<SRV>>();
|
||||
TreeMap<String, ArrayList<String>> ips4 = new TreeMap<String, ArrayList<String>>();
|
||||
TreeMap<String, ArrayList<String>> ips6 = new TreeMap<String, ArrayList<String>>();
|
||||
|
||||
for (Record[] rrset : new Record[][] { message.getAnswers(),
|
||||
message.getAdditionalResourceRecords() }) {
|
||||
for (Record rr : rrset) {
|
||||
Data d = rr.getPayload();
|
||||
if (d instanceof SRV
|
||||
&& NameUtil.idnEquals(qname, rr.getName())) {
|
||||
SRV srv = (SRV) d;
|
||||
if (!priorities.containsKey(srv.getPriority())) {
|
||||
priorities.put(srv.getPriority(),
|
||||
new ArrayList<SRV>(2));
|
||||
}
|
||||
priorities.get(srv.getPriority()).add(srv);
|
||||
}
|
||||
if (d instanceof A) {
|
||||
A arecord = (A) d;
|
||||
if (!ips4.containsKey(rr.getName())) {
|
||||
ips4.put(rr.getName(), new ArrayList<String>(3));
|
||||
}
|
||||
ips4.get(rr.getName()).add(arecord.toString());
|
||||
}
|
||||
if (d instanceof AAAA) {
|
||||
AAAA aaaa = (AAAA) d;
|
||||
if (!ips6.containsKey(rr.getName())) {
|
||||
ips6.put(rr.getName(), new ArrayList<String>(3));
|
||||
}
|
||||
ips6.get(rr.getName()).add("[" + aaaa.toString() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Random rnd = new Random();
|
||||
ArrayList<SRV> result = new ArrayList<SRV>(
|
||||
priorities.size() * 2 + 1);
|
||||
for (ArrayList<SRV> s : priorities.values()) {
|
||||
|
||||
// trivial case
|
||||
if (s.size() <= 1) {
|
||||
result.addAll(s);
|
||||
continue;
|
||||
}
|
||||
|
||||
long totalweight = 0l;
|
||||
for (SRV srv : s) {
|
||||
totalweight += srv.getWeight();
|
||||
}
|
||||
|
||||
while (totalweight > 0l && s.size() > 0) {
|
||||
long p = (rnd.nextLong() & 0x7fffffffffffffffl)
|
||||
% totalweight;
|
||||
int i = 0;
|
||||
while (p > 0) {
|
||||
p -= s.get(i++).getPriority();
|
||||
}
|
||||
i--;
|
||||
// remove is expensive, but we have only a few entries
|
||||
// anyway
|
||||
SRV srv = s.remove(i);
|
||||
totalweight -= srv.getWeight();
|
||||
result.add(srv);
|
||||
}
|
||||
|
||||
Collections.shuffle(s, rnd);
|
||||
result.addAll(s);
|
||||
|
||||
}
|
||||
|
||||
if (result.size() == 0) {
|
||||
namePort.putString("error", "nosrv");
|
||||
return namePort;
|
||||
}
|
||||
// we now have a list of servers to try :-)
|
||||
|
||||
// classic name/port pair
|
||||
String resultName = result.get(0).getName();
|
||||
namePort.putString("name", resultName);
|
||||
namePort.putInt("port", result.get(0).getPort());
|
||||
|
||||
if (ips4.containsKey(resultName)) {
|
||||
// we have an ip!
|
||||
ArrayList<String> ip = ips4.get(resultName);
|
||||
Collections.shuffle(ip, rnd);
|
||||
namePort.putString("ipv4", ip.get(0));
|
||||
}
|
||||
if (ips6.containsKey(resultName)) {
|
||||
ArrayList<String> ip = ips6.get(resultName);
|
||||
Collections.shuffle(ip, rnd);
|
||||
namePort.putString("ipv6", ip.get(0));
|
||||
}
|
||||
|
||||
// add all other records
|
||||
int i = 0;
|
||||
for (SRV srv : result) {
|
||||
namePort.putString("name" + i, srv.getName());
|
||||
namePort.putInt("port" + i, srv.getPort());
|
||||
i++;
|
||||
}
|
||||
|
||||
} catch (SocketTimeoutException e) {
|
||||
namePort.putString("error", "timeout");
|
||||
} catch (Exception e) {
|
||||
namePort.putString("error", "unhandled");
|
||||
}
|
||||
return namePort;
|
||||
}
|
||||
|
||||
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = hexArray[v >>> 4];
|
||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class ExceptionHandler implements UncaughtExceptionHandler {
|
||||
|
||||
private UncaughtExceptionHandler defaultHandler;
|
||||
private Context context;
|
||||
|
||||
public ExceptionHandler(Context context) {
|
||||
this.context = context;
|
||||
this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable ex) {
|
||||
Writer result = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(result);
|
||||
ex.printStackTrace(printWriter);
|
||||
String stacktrace = result.toString();
|
||||
printWriter.close();
|
||||
try {
|
||||
OutputStream os = context.openFileOutput("stacktrace.txt",
|
||||
Context.MODE_PRIVATE);
|
||||
os.write(stacktrace.getBytes());
|
||||
} catch (FileNotFoundException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.defaultHandler.uncaughtException(thread, ex);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
|
||||
public class ExceptionHelper {
|
||||
public static void init(Context context) {
|
||||
if (!(Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler)) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(
|
||||
context));
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkForCrash(Context context,
|
||||
final XmppConnectionService service) {
|
||||
try {
|
||||
final SharedPreferences preferences = PreferenceManager
|
||||
.getDefaultSharedPreferences(context);
|
||||
boolean neverSend = preferences.getBoolean("never_send", false);
|
||||
if (neverSend) {
|
||||
return;
|
||||
}
|
||||
List<Account> accounts = service.getAccounts();
|
||||
Account account = null;
|
||||
for (int i = 0; i < accounts.size(); ++i) {
|
||||
if (!accounts.get(i).isOptionSet(Account.OPTION_DISABLED)) {
|
||||
account = accounts.get(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (account == null) {
|
||||
return;
|
||||
}
|
||||
final Account finalAccount = account;
|
||||
FileInputStream file = context.openFileInput("stacktrace.txt");
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(file);
|
||||
BufferedReader stacktrace = new BufferedReader(inputStreamReader);
|
||||
final StringBuilder report = new StringBuilder();
|
||||
PackageManager pm = context.getPackageManager();
|
||||
PackageInfo packageInfo = null;
|
||||
try {
|
||||
packageInfo = pm.getPackageInfo(context.getPackageName(), 0);
|
||||
report.append("Version: " + packageInfo.versionName + '\n');
|
||||
report.append("Last Update: "
|
||||
+ DateUtils.formatDateTime(context,
|
||||
packageInfo.lastUpdateTime,
|
||||
DateUtils.FORMAT_SHOW_TIME
|
||||
| DateUtils.FORMAT_SHOW_DATE) + '\n');
|
||||
} catch (NameNotFoundException e) {
|
||||
}
|
||||
String line;
|
||||
while ((line = stacktrace.readLine()) != null) {
|
||||
report.append(line);
|
||||
report.append('\n');
|
||||
}
|
||||
file.close();
|
||||
context.deleteFile("stacktrace.txt");
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(context.getString(R.string.crash_report_title));
|
||||
builder.setMessage(context.getText(R.string.crash_report_message));
|
||||
builder.setPositiveButton(context.getText(R.string.send_now),
|
||||
new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
Log.d(Config.LOGTAG, "using account="
|
||||
+ finalAccount.getJid()
|
||||
+ " to send in stack trace");
|
||||
Conversation conversation = service
|
||||
.findOrCreateConversation(finalAccount,
|
||||
"bugs@siacs.eu", false);
|
||||
Message message = new Message(conversation, report
|
||||
.toString(), Message.ENCRYPTION_NONE);
|
||||
service.sendMessage(message);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(context.getText(R.string.send_never),
|
||||
new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
preferences.edit().putBoolean("never_send", true)
|
||||
.commit();
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
} catch (FileNotFoundException e) {
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
public interface OnPhoneContactsLoadedListener {
|
||||
public void onPhoneContactsLoaded(List<Bundle> phoneContacts);
|
||||
}
|
@ -0,0 +1,327 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SecureRandomSpi;
|
||||
import java.security.Security;
|
||||
|
||||
/**
|
||||
* Fixes for the output of the default PRNG having low entropy.
|
||||
*
|
||||
* The fixes need to be applied via {@link #apply()} before any use of Java
|
||||
* Cryptography Architecture primitives. A good place to invoke them is in the
|
||||
* application's {@code onCreate}.
|
||||
*/
|
||||
public final class PRNGFixes {
|
||||
|
||||
private static final int VERSION_CODE_JELLY_BEAN = 16;
|
||||
private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
|
||||
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial();
|
||||
|
||||
/** Hidden constructor to prevent instantiation. */
|
||||
private PRNGFixes() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all fixes.
|
||||
*
|
||||
* @throws SecurityException
|
||||
* if a fix is needed but could not be applied.
|
||||
*/
|
||||
public static void apply() {
|
||||
applyOpenSSLFix();
|
||||
installLinuxPRNGSecureRandom();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
|
||||
* fix is not needed.
|
||||
*
|
||||
* @throws SecurityException
|
||||
* if the fix is needed but could not be applied.
|
||||
*/
|
||||
private static void applyOpenSSLFix() throws SecurityException {
|
||||
if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
|
||||
|| (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
|
||||
// No need to apply the fix
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Mix in the device- and invocation-specific seed.
|
||||
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||
.getMethod("RAND_seed", byte[].class)
|
||||
.invoke(null, generateSeed());
|
||||
|
||||
// Mix output of Linux PRNG into OpenSSL's PRNG
|
||||
int bytesRead = (Integer) Class
|
||||
.forName(
|
||||
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||
.getMethod("RAND_load_file", String.class, long.class)
|
||||
.invoke(null, "/dev/urandom", 1024);
|
||||
if (bytesRead != 1024) {
|
||||
throw new IOException(
|
||||
"Unexpected number of bytes read from Linux PRNG: "
|
||||
+ bytesRead);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new SecurityException("Failed to seed OpenSSL PRNG", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
|
||||
* default. Does nothing if the implementation is already the default or if
|
||||
* there is not need to install the implementation.
|
||||
*
|
||||
* @throws SecurityException
|
||||
* if the fix is needed but could not be applied.
|
||||
*/
|
||||
private static void installLinuxPRNGSecureRandom() throws SecurityException {
|
||||
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
|
||||
// No need to apply the fix
|
||||
return;
|
||||
}
|
||||
|
||||
// Install a Linux PRNG-based SecureRandom implementation as the
|
||||
// default, if not yet installed.
|
||||
Provider[] secureRandomProviders = Security
|
||||
.getProviders("SecureRandom.SHA1PRNG");
|
||||
if ((secureRandomProviders == null)
|
||||
|| (secureRandomProviders.length < 1)
|
||||
|| (!LinuxPRNGSecureRandomProvider.class
|
||||
.equals(secureRandomProviders[0].getClass()))) {
|
||||
Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
|
||||
}
|
||||
|
||||
// Assert that new SecureRandom() and
|
||||
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
|
||||
// by the Linux PRNG-based SecureRandom implementation.
|
||||
SecureRandom rng1 = new SecureRandom();
|
||||
if (!LinuxPRNGSecureRandomProvider.class.equals(rng1.getProvider()
|
||||
.getClass())) {
|
||||
throw new SecurityException(
|
||||
"new SecureRandom() backed by wrong Provider: "
|
||||
+ rng1.getProvider().getClass());
|
||||
}
|
||||
|
||||
SecureRandom rng2;
|
||||
try {
|
||||
rng2 = SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new SecurityException("SHA1PRNG not available", e);
|
||||
}
|
||||
if (!LinuxPRNGSecureRandomProvider.class.equals(rng2.getProvider()
|
||||
.getClass())) {
|
||||
throw new SecurityException(
|
||||
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
|
||||
+ " Provider: " + rng2.getProvider().getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code Provider} of {@code SecureRandom} engines which pass through all
|
||||
* requests to the Linux PRNG.
|
||||
*/
|
||||
private static class LinuxPRNGSecureRandomProvider extends Provider {
|
||||
|
||||
public LinuxPRNGSecureRandomProvider() {
|
||||
super("LinuxPRNG", 1.0,
|
||||
"A Linux-specific random number provider that uses"
|
||||
+ " /dev/urandom");
|
||||
// Although /dev/urandom is not a SHA-1 PRNG, some apps
|
||||
// explicitly request a SHA1PRNG SecureRandom and we thus need to
|
||||
// prevent them from getting the default implementation whose output
|
||||
// may have low entropy.
|
||||
put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
|
||||
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SecureRandomSpi} which passes all requests to the Linux PRNG (
|
||||
* {@code /dev/urandom}).
|
||||
*/
|
||||
public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
|
||||
|
||||
/*
|
||||
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
|
||||
* are passed through to the Linux PRNG (/dev/urandom). Instances of
|
||||
* this class seed themselves by mixing in the current time, PID, UID,
|
||||
* build fingerprint, and hardware serial number (where available) into
|
||||
* Linux PRNG.
|
||||
*
|
||||
* Concurrency: Read requests to the underlying Linux PRNG are
|
||||
* serialized (on sLock) to ensure that multiple threads do not get
|
||||
* duplicated PRNG output.
|
||||
*/
|
||||
|
||||
private static final File URANDOM_FILE = new File("/dev/urandom");
|
||||
|
||||
private static final Object sLock = new Object();
|
||||
|
||||
/**
|
||||
* Input stream for reading from Linux PRNG or {@code null} if not yet
|
||||
* opened.
|
||||
*
|
||||
* @GuardedBy("sLock")
|
||||
*/
|
||||
private static DataInputStream sUrandomIn;
|
||||
|
||||
/**
|
||||
* Output stream for writing to Linux PRNG or {@code null} if not yet
|
||||
* opened.
|
||||
*
|
||||
* @GuardedBy("sLock")
|
||||
*/
|
||||
private static OutputStream sUrandomOut;
|
||||
|
||||
/**
|
||||
* Whether this engine instance has been seeded. This is needed because
|
||||
* each instance needs to seed itself if the client does not explicitly
|
||||
* seed it.
|
||||
*/
|
||||
private boolean mSeeded;
|
||||
|
||||
@Override
|
||||
protected void engineSetSeed(byte[] bytes) {
|
||||
try {
|
||||
OutputStream out;
|
||||
synchronized (sLock) {
|
||||
out = getUrandomOutputStream();
|
||||
}
|
||||
out.write(bytes);
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
// On a small fraction of devices /dev/urandom is not writable.
|
||||
// Log and ignore.
|
||||
Log.w(PRNGFixes.class.getSimpleName(),
|
||||
"Failed to mix seed into " + URANDOM_FILE);
|
||||
} finally {
|
||||
mSeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineNextBytes(byte[] bytes) {
|
||||
if (!mSeeded) {
|
||||
// Mix in the device- and invocation-specific seed.
|
||||
engineSetSeed(generateSeed());
|
||||
}
|
||||
|
||||
try {
|
||||
DataInputStream in;
|
||||
synchronized (sLock) {
|
||||
in = getUrandomInputStream();
|
||||
}
|
||||
synchronized (in) {
|
||||
in.readFully(bytes);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new SecurityException("Failed to read from "
|
||||
+ URANDOM_FILE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineGenerateSeed(int size) {
|
||||
byte[] seed = new byte[size];
|
||||
engineNextBytes(seed);
|
||||
return seed;
|
||||
}
|
||||
|
||||
private DataInputStream getUrandomInputStream() {
|
||||
synchronized (sLock) {
|
||||
if (sUrandomIn == null) {
|
||||
// NOTE: Consider inserting a BufferedInputStream between
|
||||
// DataInputStream and FileInputStream if you need higher
|
||||
// PRNG output performance and can live with future PRNG
|
||||
// output being pulled into this process prematurely.
|
||||
try {
|
||||
sUrandomIn = new DataInputStream(new FileInputStream(
|
||||
URANDOM_FILE));
|
||||
} catch (IOException e) {
|
||||
throw new SecurityException("Failed to open "
|
||||
+ URANDOM_FILE + " for reading", e);
|
||||
}
|
||||
}
|
||||
return sUrandomIn;
|
||||
}
|
||||
}
|
||||
|
||||
private OutputStream getUrandomOutputStream() throws IOException {
|
||||
synchronized (sLock) {
|
||||
if (sUrandomOut == null) {
|
||||
sUrandomOut = new FileOutputStream(URANDOM_FILE);
|
||||
}
|
||||
return sUrandomOut;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a device- and invocation-specific seed to be mixed into the
|
||||
* Linux PRNG.
|
||||
*/
|
||||
private static byte[] generateSeed() {
|
||||
try {
|
||||
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
|
||||
DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer);
|
||||
seedBufferOut.writeLong(System.currentTimeMillis());
|
||||
seedBufferOut.writeLong(System.nanoTime());
|
||||
seedBufferOut.writeInt(Process.myPid());
|
||||
seedBufferOut.writeInt(Process.myUid());
|
||||
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
|
||||
seedBufferOut.close();
|
||||
return seedBuffer.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new SecurityException("Failed to generate seed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hardware serial number of this device.
|
||||
*
|
||||
* @return serial number or {@code null} if not available.
|
||||
*/
|
||||
private static String getDeviceSerialNumber() {
|
||||
// We're using the Reflection API because Build.SERIAL is only available
|
||||
// since API Level 9 (Gingerbread, Android 2.3).
|
||||
try {
|
||||
return (String) Build.class.getField("SERIAL").get(null);
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getBuildFingerprintAndDeviceSerial() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
String fingerprint = Build.FINGERPRINT;
|
||||
if (fingerprint != null) {
|
||||
result.append(fingerprint);
|
||||
}
|
||||
String serial = getDeviceSerialNumber();
|
||||
if (serial != null) {
|
||||
result.append(serial);
|
||||
}
|
||||
try {
|
||||
return result.toString().getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("UTF-8 encoding not supported");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.CursorLoader;
|
||||
import android.content.Loader;
|
||||
import android.content.Loader.OnLoadCompleteListener;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.Profile;
|
||||
|
||||
public class PhoneHelper {
|
||||
|
||||
public static void loadPhoneContacts(Context context,
|
||||
final OnPhoneContactsLoadedListener listener) {
|
||||
final List<Bundle> phoneContacts = new ArrayList<Bundle>();
|
||||
|
||||
final String[] PROJECTION = new String[] { ContactsContract.Data._ID,
|
||||
ContactsContract.Data.DISPLAY_NAME,
|
||||
ContactsContract.Data.PHOTO_URI,
|
||||
ContactsContract.Data.LOOKUP_KEY,
|
||||
ContactsContract.CommonDataKinds.Im.DATA };
|
||||
|
||||
final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\""
|
||||
+ ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
|
||||
+ "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL
|
||||
+ "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER
|
||||
+ "\")";
|
||||
|
||||
CursorLoader mCursorLoader = new CursorLoader(context,
|
||||
ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null,
|
||||
null);
|
||||
mCursorLoader.registerListener(0, new OnLoadCompleteListener<Cursor>() {
|
||||
|
||||
@Override
|
||||
public void onLoadComplete(Loader<Cursor> arg0, Cursor cursor) {
|
||||
if (cursor == null) {
|
||||
return;
|
||||
}
|
||||
while (cursor.moveToNext()) {
|
||||
Bundle contact = new Bundle();
|
||||
contact.putInt("phoneid", cursor.getInt(cursor
|
||||
.getColumnIndex(ContactsContract.Data._ID)));
|
||||
contact.putString(
|
||||
"displayname",
|
||||
cursor.getString(cursor
|
||||
.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)));
|
||||
contact.putString("photouri", cursor.getString(cursor
|
||||
.getColumnIndex(ContactsContract.Data.PHOTO_URI)));
|
||||
contact.putString("lookup", cursor.getString(cursor
|
||||
.getColumnIndex(ContactsContract.Data.LOOKUP_KEY)));
|
||||
|
||||
contact.putString(
|
||||
"jid",
|
||||
cursor.getString(cursor
|
||||
.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)));
|
||||
phoneContacts.add(contact);
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.onPhoneContactsLoaded(phoneContacts);
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
mCursorLoader.startLoading();
|
||||
} catch (RejectedExecutionException e) {
|
||||
if (listener != null) {
|
||||
listener.onPhoneContactsLoaded(phoneContacts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Uri getSefliUri(Context context) {
|
||||
String[] mProjection = new String[] { Profile._ID, Profile.PHOTO_URI };
|
||||
Cursor mProfileCursor = context.getContentResolver().query(
|
||||
Profile.CONTENT_URI, mProjection, null, null, null);
|
||||
|
||||
if (mProfileCursor == null || mProfileCursor.getCount() == 0) {
|
||||
return null;
|
||||
} else {
|
||||
mProfileCursor.moveToFirst();
|
||||
String uri = mProfileCursor.getString(1);
|
||||
if (uri == null) {
|
||||
return null;
|
||||
} else {
|
||||
return Uri.parse(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,225 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.ui.ConversationActivity;
|
||||
import eu.siacs.conversations.ui.ManageAccountActivity;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.text.format.DateFormat;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class UIHelper {
|
||||
private static final int SHORT_DATE_FLAGS = DateUtils.FORMAT_SHOW_DATE
|
||||
| DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL;
|
||||
private static final int FULL_DATE_FLAGS = DateUtils.FORMAT_SHOW_TIME
|
||||
| DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE;
|
||||
|
||||
public static String readableTimeDifference(Context context, long time) {
|
||||
return readableTimeDifference(context, time, false);
|
||||
}
|
||||
|
||||
public static String readableTimeDifferenceFull(Context context, long time) {
|
||||
return readableTimeDifference(context, time, true);
|
||||
}
|
||||
|
||||
private static String readableTimeDifference(Context context, long time,
|
||||
boolean fullDate) {
|
||||
if (time == 0) {
|
||||
return context.getString(R.string.just_now);
|
||||
}
|
||||
Date date = new Date(time);
|
||||
long difference = (System.currentTimeMillis() - time) / 1000;
|
||||
if (difference < 60) {
|
||||
return context.getString(R.string.just_now);
|
||||
} else if (difference < 60 * 2) {
|
||||
return context.getString(R.string.minute_ago);
|
||||
} else if (difference < 60 * 15) {
|
||||
return context.getString(R.string.minutes_ago,
|
||||
Math.round(difference / 60.0));
|
||||
} else if (today(date)) {
|
||||
java.text.DateFormat df = DateFormat.getTimeFormat(context);
|
||||
return df.format(date);
|
||||
} else {
|
||||
if (fullDate) {
|
||||
return DateUtils.formatDateTime(context, date.getTime(),
|
||||
FULL_DATE_FLAGS);
|
||||
} else {
|
||||
return DateUtils.formatDateTime(context, date.getTime(),
|
||||
SHORT_DATE_FLAGS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean today(Date date) {
|
||||
Calendar cal1 = Calendar.getInstance();
|
||||
Calendar cal2 = Calendar.getInstance();
|
||||
cal1.setTime(date);
|
||||
cal2.setTimeInMillis(System.currentTimeMillis());
|
||||
return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
|
||||
&& cal1.get(Calendar.DAY_OF_YEAR) == cal2
|
||||
.get(Calendar.DAY_OF_YEAR);
|
||||
}
|
||||
|
||||
public static String lastseen(Context context, long time) {
|
||||
if (time == 0) {
|
||||
return context.getString(R.string.never_seen);
|
||||
}
|
||||
long difference = (System.currentTimeMillis() - time) / 1000;
|
||||
if (difference < 60) {
|
||||
return context.getString(R.string.last_seen_now);
|
||||
} else if (difference < 60 * 2) {
|
||||
return context.getString(R.string.last_seen_min);
|
||||
} else if (difference < 60 * 60) {
|
||||
return context.getString(R.string.last_seen_mins,
|
||||
Math.round(difference / 60.0));
|
||||
} else if (difference < 60 * 60 * 2) {
|
||||
return context.getString(R.string.last_seen_hour);
|
||||
} else if (difference < 60 * 60 * 24) {
|
||||
return context.getString(R.string.last_seen_hours,
|
||||
Math.round(difference / (60.0 * 60.0)));
|
||||
} else if (difference < 60 * 60 * 48) {
|
||||
return context.getString(R.string.last_seen_day);
|
||||
} else {
|
||||
return context.getString(R.string.last_seen_days,
|
||||
Math.round(difference / (60.0 * 60.0 * 24.0)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void showErrorNotification(Context context,
|
||||
List<Account> accounts) {
|
||||
NotificationManager mNotificationManager = (NotificationManager) context
|
||||
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
List<Account> accountsWproblems = new ArrayList<Account>();
|
||||
for (Account account : accounts) {
|
||||
if (account.hasErrorStatus()) {
|
||||
accountsWproblems.add(account);
|
||||
}
|
||||
}
|
||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
|
||||
context);
|
||||
if (accountsWproblems.size() == 0) {
|
||||
mNotificationManager.cancel(1111);
|
||||
return;
|
||||
} else if (accountsWproblems.size() == 1) {
|
||||
mBuilder.setContentTitle(context
|
||||
.getString(R.string.problem_connecting_to_account));
|
||||
mBuilder.setContentText(accountsWproblems.get(0).getJid());
|
||||
} else {
|
||||
mBuilder.setContentTitle(context
|
||||
.getString(R.string.problem_connecting_to_accounts));
|
||||
mBuilder.setContentText(context.getString(R.string.touch_to_fix));
|
||||
}
|
||||
mBuilder.setOngoing(true);
|
||||
mBuilder.setLights(0xffffffff, 2000, 4000);
|
||||
mBuilder.setSmallIcon(R.drawable.ic_notification);
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
||||
stackBuilder.addParentStack(ConversationActivity.class);
|
||||
|
||||
Intent manageAccountsIntent = new Intent(context,
|
||||
ManageAccountActivity.class);
|
||||
stackBuilder.addNextIntent(manageAccountsIntent);
|
||||
|
||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
mBuilder.setContentIntent(resultPendingIntent);
|
||||
Notification notification = mBuilder.build();
|
||||
mNotificationManager.notify(1111, notification);
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
public static AlertDialog getVerifyFingerprintDialog(
|
||||
final ConversationActivity activity,
|
||||
final Conversation conversation, final View msg) {
|
||||
final Contact contact = conversation.getContact();
|
||||
final Account account = conversation.getAccount();
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setTitle("Verify fingerprint");
|
||||
LayoutInflater inflater = activity.getLayoutInflater();
|
||||
View view = inflater.inflate(R.layout.dialog_verify_otr, null);
|
||||
TextView jid = (TextView) view.findViewById(R.id.verify_otr_jid);
|
||||
TextView fingerprint = (TextView) view
|
||||
.findViewById(R.id.verify_otr_fingerprint);
|
||||
TextView yourprint = (TextView) view
|
||||
.findViewById(R.id.verify_otr_yourprint);
|
||||
|
||||
jid.setText(contact.getJid());
|
||||
fingerprint.setText(conversation.getOtrFingerprint());
|
||||
yourprint.setText(account.getOtrFingerprint());
|
||||
builder.setNegativeButton("Cancel", null);
|
||||
builder.setPositiveButton("Verify", new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
contact.addOtrFingerprint(conversation.getOtrFingerprint());
|
||||
msg.setVisibility(View.GONE);
|
||||
activity.xmppConnectionService.syncRosterToDisk(account);
|
||||
}
|
||||
});
|
||||
builder.setView(view);
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
private final static class EmoticonPattern {
|
||||
Pattern pattern;
|
||||
String replacement;
|
||||
|
||||
EmoticonPattern(String ascii, int unicode) {
|
||||
this.pattern = Pattern.compile("(?<=(^|\\s))" + ascii
|
||||
+ "(?=(\\s|$))");
|
||||
this.replacement = new String(new int[] { unicode, }, 0, 1);
|
||||
}
|
||||
|
||||
String replaceAll(String body) {
|
||||
return pattern.matcher(body).replaceAll(replacement);
|
||||
}
|
||||
}
|
||||
|
||||
private static final EmoticonPattern[] patterns = new EmoticonPattern[] {
|
||||
new EmoticonPattern(":-?D", 0x1f600),
|
||||
new EmoticonPattern("\\^\\^", 0x1f601),
|
||||
new EmoticonPattern(":'D", 0x1f602),
|
||||
new EmoticonPattern("\\]-?D", 0x1f608),
|
||||
new EmoticonPattern(";-?\\)", 0x1f609),
|
||||
new EmoticonPattern(":-?\\)", 0x1f60a),
|
||||
new EmoticonPattern("[B8]-?\\)", 0x1f60e),
|
||||
new EmoticonPattern(":-?\\|", 0x1f610),
|
||||
new EmoticonPattern(":-?[/\\\\]", 0x1f615),
|
||||
new EmoticonPattern(":-?\\*", 0x1f617),
|
||||
new EmoticonPattern(":-?[Ppb]", 0x1f61b),
|
||||
new EmoticonPattern(":-?\\(", 0x1f61e),
|
||||
new EmoticonPattern(":-?[0Oo]", 0x1f62e),
|
||||
new EmoticonPattern("\\\\o/", 0x1F631), };
|
||||
|
||||
public static String transformAsciiEmoticons(String body) {
|
||||
if (body != null) {
|
||||
for (EmoticonPattern p : patterns) {
|
||||
body = p.replaceAll(body);
|
||||
}
|
||||
body = body.trim();
|
||||
}
|
||||
return body;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Validator {
|
||||
public static final Pattern VALID_JID = Pattern.compile(
|
||||
"^[^@/<>'\"\\s]+@[^@/<>'\"\\s]+$", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
public static boolean isValidJid(String jid) {
|
||||
Matcher matcher = VALID_JID.matcher(jid);
|
||||
return matcher.find();
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package eu.siacs.conversations.utils;
|
||||
|
||||
public class XmlHelper {
|
||||
public static String encodeEntities(String content) {
|
||||
content = content.replace("&", "&");
|
||||
content = content.replace("<", "<");
|
||||
content = content.replace(">", ">");
|
||||
content = content.replace("\"", """);
|
||||
content = content.replace("'", "'");
|
||||
return content;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package eu.siacs.conversations.utils.zlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
/**
|
||||
* ZLibInputStream is a zlib and input stream compatible version of an
|
||||
* InflaterInputStream. This class solves the incompatibility between
|
||||
* {@link InputStream#available()} and {@link InflaterInputStream#available()}.
|
||||
*/
|
||||
public class ZLibInputStream extends InflaterInputStream {
|
||||
|
||||
/**
|
||||
* Construct a ZLibInputStream, reading data from the underlying stream.
|
||||
*
|
||||
* @param is
|
||||
* The {@code InputStream} to read data from.
|
||||
* @throws IOException
|
||||
* If an {@code IOException} occurs.
|
||||
*/
|
||||
public ZLibInputStream(InputStream is) throws IOException {
|
||||
super(is, new Inflater(), 512);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a more InputStream compatible version of available. A return
|
||||
* value of 1 means that it is likly to read one byte without blocking, 0
|
||||
* means that the system is known to block for more input.
|
||||
*
|
||||
* @return 0 if no data is available, 1 otherwise
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
/*
|
||||
* This is one of the funny code blocks. InflaterInputStream.available
|
||||
* violates the contract of InputStream.available, which breaks kXML2.
|
||||
*
|
||||
* I'm not sure who's to blame, oracle/sun for a broken api or the
|
||||
* google guys for mixing a sun bug with a xml reader that can't handle
|
||||
* it....
|
||||
*
|
||||
* Anyway, this simple if breaks suns distorted reality, but helps to
|
||||
* use the api as intended.
|
||||
*/
|
||||
if (inf.needsInput()) {
|
||||
return 0;
|
||||
}
|
||||
return super.available();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package eu.siacs.conversations.utils.zlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Android 2.2 includes Java7 FLUSH_SYNC option, which will be used by this
|
||||
* Implementation, preferable via reflection. The @hide was remove in API level
|
||||
* 19. This class might thus go away in the future.
|
||||
* </p>
|
||||
* <p>
|
||||
* Please use {@link ZLibOutputStream#SUPPORTED} to check for flush
|
||||
* compatibility.
|
||||
* </p>
|
||||
*/
|
||||
public class ZLibOutputStream extends DeflaterOutputStream {
|
||||
|
||||
/**
|
||||
* The reflection based flush method.
|
||||
*/
|
||||
|
||||
private final static Method method;
|
||||
/**
|
||||
* SUPPORTED is true if a flush compatible method exists.
|
||||
*/
|
||||
public final static boolean SUPPORTED;
|
||||
|
||||
/**
|
||||
* Static block to initialize {@link #SUPPORTED} and {@link #method}.
|
||||
*/
|
||||
static {
|
||||
Method m = null;
|
||||
try {
|
||||
m = Deflater.class.getMethod("deflate", byte[].class, int.class,
|
||||
int.class, int.class);
|
||||
} catch (SecurityException e) {
|
||||
} catch (NoSuchMethodException e) {
|
||||
}
|
||||
method = m;
|
||||
SUPPORTED = (method != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ZLib compatible output stream wrapping the given low level
|
||||
* stream. ZLib compatiblity means we will send a zlib header.
|
||||
*
|
||||
* @param os
|
||||
* OutputStream The underlying stream.
|
||||
* @throws IOException
|
||||
* In case of a lowlevel transfer problem.
|
||||
* @throws NoSuchAlgorithmException
|
||||
* In case of a {@link Deflater} error.
|
||||
*/
|
||||
public ZLibOutputStream(OutputStream os) throws IOException,
|
||||
NoSuchAlgorithmException {
|
||||
super(os, new Deflater(Deflater.BEST_COMPRESSION));
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the given stream, preferring Java7 FLUSH_SYNC if available.
|
||||
*
|
||||
* @throws IOException
|
||||
* In case of a lowlevel exception.
|
||||
*/
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
if (!SUPPORTED) {
|
||||
super.flush();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
int count = 0;
|
||||
do {
|
||||
count = (Integer) method.invoke(def, buf, 0, buf.length, 3);
|
||||
if (count > 0) {
|
||||
out.write(buf, 0, count);
|
||||
}
|
||||
} while (count > 0);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IOException("Can't flush");
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IOException("Can't flush");
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IOException("Can't flush");
|
||||
}
|
||||
super.flush();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
package eu.siacs.conversations.xml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.utils.XmlHelper;
|
||||
|
||||
public class Element {
|
||||
protected String name;
|
||||
protected Hashtable<String, String> attributes = new Hashtable<String, String>();
|
||||
protected String content;
|
||||
protected List<Element> children = new ArrayList<Element>();
|
||||
|
||||
public Element(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Element addChild(Element child) {
|
||||
this.content = null;
|
||||
children.add(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
public Element addChild(String name) {
|
||||
this.content = null;
|
||||
Element child = new Element(name);
|
||||
children.add(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
public Element addChild(String name, String xmlns) {
|
||||
this.content = null;
|
||||
Element child = new Element(name);
|
||||
child.setAttribute("xmlns", xmlns);
|
||||
children.add(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
public Element setContent(String content) {
|
||||
this.content = content;
|
||||
this.children.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Element findChild(String name) {
|
||||
for (Element child : this.children) {
|
||||
if (child.getName().equals(name)) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Element findChild(String name, String xmlns) {
|
||||
for (Element child : this.children) {
|
||||
if (child.getName().equals(name)
|
||||
&& (child.getAttribute("xmlns").equals(xmlns))) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean hasChild(String name) {
|
||||
return findChild(name) != null;
|
||||
}
|
||||
|
||||
public boolean hasChild(String name, String xmlns) {
|
||||
return findChild(name, xmlns) != null;
|
||||
}
|
||||
|
||||
public List<Element> getChildren() {
|
||||
return this.children;
|
||||
}
|
||||
|
||||
public Element setChildren(List<Element> children) {
|
||||
this.children = children;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public Element setAttribute(String name, String value) {
|
||||
if (name != null && value != null) {
|
||||
this.attributes.put(name, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Element setAttributes(Hashtable<String, String> attributes) {
|
||||
this.attributes = attributes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getAttribute(String name) {
|
||||
if (this.attributes.containsKey(name)) {
|
||||
return this.attributes.get(name);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Hashtable<String, String> getAttributes() {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder elementOutput = new StringBuilder();
|
||||
if ((content == null) && (children.size() == 0)) {
|
||||
Tag emptyTag = Tag.empty(name);
|
||||
emptyTag.setAtttributes(this.attributes);
|
||||
elementOutput.append(emptyTag.toString());
|
||||
} else {
|
||||
Tag startTag = Tag.start(name);
|
||||
startTag.setAtttributes(this.attributes);
|
||||
elementOutput.append(startTag);
|
||||
if (content != null) {
|
||||
elementOutput.append(XmlHelper.encodeEntities(content));
|
||||
} else {
|
||||
for (Element child : children) {
|
||||
elementOutput.append(child.toString());
|
||||
}
|
||||
}
|
||||
Tag endTag = Tag.end(name);
|
||||
elementOutput.append(endTag);
|
||||
}
|
||||
return elementOutput.toString();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void clearChildren() {
|
||||
this.children.clear();
|
||||
}
|
||||
|
||||
public void setAttribute(String name, long value) {
|
||||
this.setAttribute(name, Long.toString(value));
|
||||
}
|
||||
|
||||
public void setAttribute(String name, int value) {
|
||||
this.setAttribute(name, Integer.toString(value));
|
||||
}
|
||||
}
|
104
conversations/src/main/java/eu/siacs/conversations/xml/Tag.java
Normal file
104
conversations/src/main/java/eu/siacs/conversations/xml/Tag.java
Normal file
@ -0,0 +1,104 @@
|
||||
package eu.siacs.conversations.xml;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import eu.siacs.conversations.utils.XmlHelper;
|
||||
|
||||
public class Tag {
|
||||
public static final int NO = -1;
|
||||
public static final int START = 0;
|
||||
public static final int END = 1;
|
||||
public static final int EMPTY = 2;
|
||||
|
||||
protected int type;
|
||||
protected String name;
|
||||
protected Hashtable<String, String> attributes = new Hashtable<String, String>();
|
||||
|
||||
protected Tag(int type, String name) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static Tag no(String text) {
|
||||
return new Tag(NO, text);
|
||||
}
|
||||
|
||||
public static Tag start(String name) {
|
||||
return new Tag(START, name);
|
||||
}
|
||||
|
||||
public static Tag end(String name) {
|
||||
return new Tag(END, name);
|
||||
}
|
||||
|
||||
public static Tag empty(String name) {
|
||||
return new Tag(EMPTY, name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getAttribute(String attrName) {
|
||||
return this.attributes.get(attrName);
|
||||
}
|
||||
|
||||
public Tag setAttribute(String attrName, String attrValue) {
|
||||
this.attributes.put(attrName, attrValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Tag setAtttributes(Hashtable<String, String> attributes) {
|
||||
this.attributes = attributes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isStart(String needle) {
|
||||
if (needle == null)
|
||||
return false;
|
||||
return (this.type == START) && (needle.equals(this.name));
|
||||
}
|
||||
|
||||
public boolean isEnd(String needle) {
|
||||
if (needle == null)
|
||||
return false;
|
||||
return (this.type == END) && (needle.equals(this.name));
|
||||
}
|
||||
|
||||
public boolean isNo() {
|
||||
return (this.type == NO);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder tagOutput = new StringBuilder();
|
||||
tagOutput.append('<');
|
||||
if (type == END) {
|
||||
tagOutput.append('/');
|
||||
}
|
||||
tagOutput.append(name);
|
||||
if (type != END) {
|
||||
Set<Entry<String, String>> attributeSet = attributes.entrySet();
|
||||
Iterator<Entry<String, String>> it = attributeSet.iterator();
|
||||
while (it.hasNext()) {
|
||||
Entry<String, String> entry = it.next();
|
||||
tagOutput.append(' ');
|
||||
tagOutput.append(entry.getKey());
|
||||
tagOutput.append("=\"");
|
||||
tagOutput.append(XmlHelper.encodeEntities(entry.getValue()));
|
||||
tagOutput.append('"');
|
||||
}
|
||||
}
|
||||
if (type == EMPTY) {
|
||||
tagOutput.append('/');
|
||||
}
|
||||
tagOutput.append('>');
|
||||
return tagOutput.toString();
|
||||
}
|
||||
|
||||
public Hashtable<String, String> getAttributes() {
|
||||
return this.attributes;
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package eu.siacs.conversations.xml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
|
||||
|
||||
public class TagWriter {
|
||||
|
||||
private OutputStream plainOutputStream;
|
||||
private OutputStreamWriter outputStream;
|
||||
private boolean finshed = false;
|
||||
private LinkedBlockingQueue<AbstractStanza> writeQueue = new LinkedBlockingQueue<AbstractStanza>();
|
||||
private Thread asyncStanzaWriter = new Thread() {
|
||||
private boolean shouldStop = false;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!shouldStop) {
|
||||
if ((finshed) && (writeQueue.size() == 0)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
AbstractStanza output = writeQueue.take();
|
||||
if (outputStream == null) {
|
||||
shouldStop = true;
|
||||
} else {
|
||||
outputStream.write(output.toString());
|
||||
outputStream.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
shouldStop = true;
|
||||
} catch (InterruptedException e) {
|
||||
shouldStop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public TagWriter() {
|
||||
}
|
||||
|
||||
public void setOutputStream(OutputStream out) throws IOException {
|
||||
if (out == null) {
|
||||
throw new IOException();
|
||||
}
|
||||
this.plainOutputStream = out;
|
||||
this.outputStream = new OutputStreamWriter(out);
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
if (this.plainOutputStream == null) {
|
||||
throw new IOException();
|
||||
}
|
||||
return this.plainOutputStream;
|
||||
}
|
||||
|
||||
public TagWriter beginDocument() throws IOException {
|
||||
if (outputStream == null) {
|
||||
throw new IOException("output stream was null");
|
||||
}
|
||||
outputStream.write("<?xml version='1.0'?>");
|
||||
outputStream.flush();
|
||||
return this;
|
||||
}
|
||||
|
||||
public TagWriter writeTag(Tag tag) throws IOException {
|
||||
if (outputStream == null) {
|
||||
throw new IOException("output stream was null");
|
||||
}
|
||||
outputStream.write(tag.toString());
|
||||
outputStream.flush();
|
||||
return this;
|
||||
}
|
||||
|
||||
public TagWriter writeElement(Element element) throws IOException {
|
||||
if (outputStream == null) {
|
||||
throw new IOException("output stream was null");
|
||||
}
|
||||
outputStream.write(element.toString());
|
||||
outputStream.flush();
|
||||
return this;
|
||||
}
|
||||
|
||||
public TagWriter writeStanzaAsync(AbstractStanza stanza) {
|
||||
if (finshed) {
|
||||
return this;
|
||||
} else {
|
||||
if (!asyncStanzaWriter.isAlive()) {
|
||||
try {
|
||||
asyncStanzaWriter.start();
|
||||
} catch (IllegalThreadStateException e) {
|
||||
// already started
|
||||
}
|
||||
}
|
||||
writeQueue.add(stanza);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
this.finshed = true;
|
||||
}
|
||||
|
||||
public boolean finished() {
|
||||
return (this.writeQueue.size() == 0);
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return outputStream != null;
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package eu.siacs.conversations.xml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
|
||||
public class XmlReader {
|
||||
private XmlPullParser parser;
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
private InputStream is;
|
||||
|
||||
public XmlReader(WakeLock wakeLock) {
|
||||
this.parser = Xml.newPullParser();
|
||||
try {
|
||||
this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES,
|
||||
true);
|
||||
} catch (XmlPullParserException e) {
|
||||
Log.d(Config.LOGTAG, "error setting namespace feature on parser");
|
||||
}
|
||||
this.wakeLock = wakeLock;
|
||||
}
|
||||
|
||||
public void setInputStream(InputStream inputStream) throws IOException {
|
||||
if (inputStream == null) {
|
||||
throw new IOException();
|
||||
}
|
||||
this.is = inputStream;
|
||||
try {
|
||||
parser.setInput(new InputStreamReader(this.is));
|
||||
} catch (XmlPullParserException e) {
|
||||
throw new IOException("error resetting parser");
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream getInputStream() throws IOException {
|
||||
if (this.is == null) {
|
||||
throw new IOException();
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
public void reset() throws IOException {
|
||||
if (this.is == null) {
|
||||
throw new IOException();
|
||||
}
|
||||
try {
|
||||
parser.setInput(new InputStreamReader(this.is));
|
||||
} catch (XmlPullParserException e) {
|
||||
throw new IOException("error resetting parser");
|
||||
}
|
||||
}
|
||||
|
||||
public Tag readTag() throws XmlPullParserException, IOException {
|
||||
if (wakeLock.isHeld()) {
|
||||
try {
|
||||
wakeLock.release();
|
||||
} catch (RuntimeException re) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
while (this.is != null
|
||||
&& parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
wakeLock.acquire();
|
||||
if (parser.getEventType() == XmlPullParser.START_TAG) {
|
||||
Tag tag = Tag.start(parser.getName());
|
||||
for (int i = 0; i < parser.getAttributeCount(); ++i) {
|
||||
tag.setAttribute(parser.getAttributeName(i),
|
||||
parser.getAttributeValue(i));
|
||||
}
|
||||
String xmlns = parser.getNamespace();
|
||||
if (xmlns != null) {
|
||||
tag.setAttribute("xmlns", xmlns);
|
||||
}
|
||||
return tag;
|
||||
} else if (parser.getEventType() == XmlPullParser.END_TAG) {
|
||||
Tag tag = Tag.end(parser.getName());
|
||||
return tag;
|
||||
} else if (parser.getEventType() == XmlPullParser.TEXT) {
|
||||
Tag tag = Tag.no(parser.getText());
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
if (wakeLock.isHeld()) {
|
||||
try {
|
||||
wakeLock.release();
|
||||
} catch (RuntimeException re) {
|
||||
}
|
||||
}
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
throw new IOException(
|
||||
"xml parser mishandled ArrayIndexOufOfBounds", e);
|
||||
} catch (StringIndexOutOfBoundsException e) {
|
||||
throw new IOException(
|
||||
"xml parser mishandled StringIndexOufOfBounds", e);
|
||||
} catch (NullPointerException e) {
|
||||
throw new IOException("xml parser mishandled NullPointerException",
|
||||
e);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
throw new IOException("xml parser mishandled IndexOutOfBound", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Element readElement(Tag currentTag) throws XmlPullParserException,
|
||||
IOException {
|
||||
Element element = new Element(currentTag.getName());
|
||||
element.setAttributes(currentTag.getAttributes());
|
||||
Tag nextTag = this.readTag();
|
||||
if (nextTag == null) {
|
||||
throw new IOException("unterupted mid tag");
|
||||
}
|
||||
if (nextTag.isNo()) {
|
||||
element.setContent(nextTag.getName());
|
||||
nextTag = this.readTag();
|
||||
if (nextTag == null) {
|
||||
throw new IOException("unterupted mid tag");
|
||||
}
|
||||
}
|
||||
while (!nextTag.isEnd(element.getName())) {
|
||||
if (!nextTag.isNo()) {
|
||||
Element child = this.readElement(nextTag);
|
||||
element.addChild(child);
|
||||
}
|
||||
nextTag = this.readTag();
|
||||
if (nextTag == null) {
|
||||
throw new IOException("unterupted mid tag");
|
||||
}
|
||||
}
|
||||
return element;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package eu.siacs.conversations.xmpp;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
|
||||
public interface OnBindListener {
|
||||
public void onBind(Account account);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package eu.siacs.conversations.xmpp;
|
||||
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
|
||||
public interface OnContactStatusChanged {
|
||||
public void onContactStatusChanged(Contact contact, boolean online);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package eu.siacs.conversations.xmpp;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
|
||||
public interface OnIqPacketReceived extends PacketReceived {
|
||||
public void onIqPacketReceived(Account account, IqPacket packet);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package eu.siacs.conversations.xmpp;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
|
||||
public interface OnMessageAcknowledged {
|
||||
public void onMessageAcknowledged(Account account, String id);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package eu.siacs.conversations.xmpp;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||
|
||||
public interface OnMessagePacketReceived extends PacketReceived {
|
||||
public void onMessagePacketReceived(Account account, MessagePacket packet);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package eu.siacs.conversations.xmpp;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
||||
|
||||
public interface OnPresencePacketReceived extends PacketReceived {
|
||||
public void onPresencePacketReceived(Account account, PresencePacket packet);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package eu.siacs.conversations.xmpp;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
|
||||
public interface OnStatusChanged {
|
||||
public void onStatusChanged(Account account);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package eu.siacs.conversations.xmpp;
|
||||
|
||||
public abstract interface PacketReceived {
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,143 @@
|
||||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
|
||||
public class JingleCandidate {
|
||||
|
||||
public static int TYPE_UNKNOWN;
|
||||
public static int TYPE_DIRECT = 0;
|
||||
public static int TYPE_PROXY = 1;
|
||||
|
||||
private boolean ours;
|
||||
private boolean usedByCounterpart = false;
|
||||
private String cid;
|
||||
private String host;
|
||||
private int port;
|
||||
private int type;
|
||||
private String jid;
|
||||
private int priority;
|
||||
|
||||
public JingleCandidate(String cid, boolean ours) {
|
||||
this.ours = ours;
|
||||
this.cid = cid;
|
||||
}
|
||||
|
||||
public String getCid() {
|
||||
return cid;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return this.host;
|
||||
}
|
||||
|
||||
public void setJid(String jid) {
|
||||
this.jid = jid;
|
||||
}
|
||||
|
||||
public String getJid() {
|
||||
return this.jid;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
if ("proxy".equals(type)) {
|
||||
this.type = TYPE_PROXY;
|
||||
} else if ("direct".equals(type)) {
|
||||
this.type = TYPE_DIRECT;
|
||||
} else {
|
||||
this.type = TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public void setPriority(int i) {
|
||||
this.priority = i;
|
||||
}
|
||||
|
||||
public int getPriority() {
|
||||
return this.priority;
|
||||
}
|
||||
|
||||
public boolean equals(JingleCandidate other) {
|
||||
return this.getCid().equals(other.getCid());
|
||||
}
|
||||
|
||||
public boolean equalValues(JingleCandidate other) {
|
||||
return other.getHost().equals(this.getHost())
|
||||
&& (other.getPort() == this.getPort());
|
||||
}
|
||||
|
||||
public boolean isOurs() {
|
||||
return ours;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public static List<JingleCandidate> parse(List<Element> canditates) {
|
||||
List<JingleCandidate> parsedCandidates = new ArrayList<JingleCandidate>();
|
||||
for (Element c : canditates) {
|
||||
parsedCandidates.add(JingleCandidate.parse(c));
|
||||
}
|
||||
return parsedCandidates;
|
||||
}
|
||||
|
||||
public static JingleCandidate parse(Element candidate) {
|
||||
JingleCandidate parsedCandidate = new JingleCandidate(
|
||||
candidate.getAttribute("cid"), false);
|
||||
parsedCandidate.setHost(candidate.getAttribute("host"));
|
||||
parsedCandidate.setJid(candidate.getAttribute("jid"));
|
||||
parsedCandidate.setType(candidate.getAttribute("type"));
|
||||
parsedCandidate.setPriority(Integer.parseInt(candidate
|
||||
.getAttribute("priority")));
|
||||
parsedCandidate
|
||||
.setPort(Integer.parseInt(candidate.getAttribute("port")));
|
||||
return parsedCandidate;
|
||||
}
|
||||
|
||||
public Element toElement() {
|
||||
Element element = new Element("candidate");
|
||||
element.setAttribute("cid", this.getCid());
|
||||
element.setAttribute("host", this.getHost());
|
||||
element.setAttribute("port", Integer.toString(this.getPort()));
|
||||
element.setAttribute("jid", this.getJid());
|
||||
element.setAttribute("priority", Integer.toString(this.getPriority()));
|
||||
if (this.getType() == TYPE_DIRECT) {
|
||||
element.setAttribute("type", "direct");
|
||||
} else if (this.getType() == TYPE_PROXY) {
|
||||
element.setAttribute("type", "proxy");
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
public void flagAsUsedByCounterpart() {
|
||||
this.usedByCounterpart = true;
|
||||
}
|
||||
|
||||
public boolean isUsedByCounterpart() {
|
||||
return this.usedByCounterpart;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.getHost() + ":" + this.getPort() + " (prio="
|
||||
+ this.getPriority() + ")";
|
||||
}
|
||||
}
|
@ -0,0 +1,910 @@
|
||||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Downloadable;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
|
||||
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
|
||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
|
||||
public class JingleConnection implements Downloadable {
|
||||
|
||||
private final String[] extensions = { "webp", "jpeg", "jpg", "png" };
|
||||
private final String[] cryptoExtensions = { "pgp", "gpg", "otr" };
|
||||
|
||||
private JingleConnectionManager mJingleConnectionManager;
|
||||
private XmppConnectionService mXmppConnectionService;
|
||||
|
||||
protected static final int JINGLE_STATUS_INITIATED = 0;
|
||||
protected static final int JINGLE_STATUS_ACCEPTED = 1;
|
||||
protected static final int JINGLE_STATUS_TERMINATED = 2;
|
||||
protected static final int JINGLE_STATUS_CANCELED = 3;
|
||||
protected static final int JINGLE_STATUS_FINISHED = 4;
|
||||
protected static final int JINGLE_STATUS_TRANSMITTING = 5;
|
||||
protected static final int JINGLE_STATUS_FAILED = 99;
|
||||
|
||||
private int ibbBlockSize = 4096;
|
||||
|
||||
private int mJingleStatus = -1;
|
||||
private int mStatus = -1;
|
||||
private Message message;
|
||||
private String sessionId;
|
||||
private Account account;
|
||||
private String initiator;
|
||||
private String responder;
|
||||
private List<JingleCandidate> candidates = new ArrayList<JingleCandidate>();
|
||||
private ConcurrentHashMap<String, JingleSocks5Transport> connections = new ConcurrentHashMap<String, JingleSocks5Transport>();
|
||||
|
||||
private String transportId;
|
||||
private Element fileOffer;
|
||||
private DownloadableFile file = null;
|
||||
|
||||
private String contentName;
|
||||
private String contentCreator;
|
||||
|
||||
private boolean receivedCandidate = false;
|
||||
private boolean sentCandidate = false;
|
||||
|
||||
private boolean acceptedAutomatically = false;
|
||||
|
||||
private JingleTransport transport = null;
|
||||
|
||||
private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
|
||||
|
||||
@Override
|
||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
||||
if (packet.getType() == IqPacket.TYPE_ERROR) {
|
||||
if (initiator.equals(account.getFullJid())) {
|
||||
mXmppConnectionService.markMessage(message,
|
||||
Message.STATUS_SEND_FAILED);
|
||||
}
|
||||
mJingleStatus = JINGLE_STATUS_FAILED;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() {
|
||||
|
||||
@Override
|
||||
public void onFileTransmitted(DownloadableFile file) {
|
||||
if (responder.equals(account.getFullJid())) {
|
||||
sendSuccess();
|
||||
if (acceptedAutomatically) {
|
||||
message.markUnread();
|
||||
JingleConnection.this.mXmppConnectionService
|
||||
.getNotificationService().push(message);
|
||||
}
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
int imageHeight = options.outHeight;
|
||||
int imageWidth = options.outWidth;
|
||||
message.setBody(Long.toString(file.getSize()) + ','
|
||||
+ imageWidth + ',' + imageHeight);
|
||||
mXmppConnectionService.databaseBackend.createMessage(message);
|
||||
mXmppConnectionService.markMessage(message,
|
||||
Message.STATUS_RECEIVED);
|
||||
}
|
||||
Log.d(Config.LOGTAG,
|
||||
"sucessfully transmitted file:" + file.getAbsolutePath());
|
||||
if (message.getEncryption() != Message.ENCRYPTION_PGP) {
|
||||
Intent intent = new Intent(
|
||||
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||
intent.setData(Uri.fromFile(file));
|
||||
mXmppConnectionService.sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileTransferAborted() {
|
||||
JingleConnection.this.sendCancel();
|
||||
JingleConnection.this.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
private OnProxyActivated onProxyActivated = new OnProxyActivated() {
|
||||
|
||||
@Override
|
||||
public void success() {
|
||||
if (initiator.equals(account.getFullJid())) {
|
||||
Log.d(Config.LOGTAG, "we were initiating. sending file");
|
||||
transport.send(file, onFileTransmissionSatusChanged);
|
||||
} else {
|
||||
transport.receive(file, onFileTransmissionSatusChanged);
|
||||
Log.d(Config.LOGTAG, "we were responding. receiving file");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed() {
|
||||
Log.d(Config.LOGTAG, "proxy activation failed");
|
||||
}
|
||||
};
|
||||
|
||||
public JingleConnection(JingleConnectionManager mJingleConnectionManager) {
|
||||
this.mJingleConnectionManager = mJingleConnectionManager;
|
||||
this.mXmppConnectionService = mJingleConnectionManager
|
||||
.getXmppConnectionService();
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return this.sessionId;
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return this.account;
|
||||
}
|
||||
|
||||
public String getCounterPart() {
|
||||
return this.message.getCounterpart();
|
||||
}
|
||||
|
||||
public void deliverPacket(JinglePacket packet) {
|
||||
boolean returnResult = true;
|
||||
if (packet.isAction("session-terminate")) {
|
||||
Reason reason = packet.getReason();
|
||||
if (reason != null) {
|
||||
if (reason.hasChild("cancel")) {
|
||||
this.cancel();
|
||||
} else if (reason.hasChild("success")) {
|
||||
this.receiveSuccess();
|
||||
} else {
|
||||
this.cancel();
|
||||
}
|
||||
} else {
|
||||
this.cancel();
|
||||
}
|
||||
} else if (packet.isAction("session-accept")) {
|
||||
returnResult = receiveAccept(packet);
|
||||
} else if (packet.isAction("transport-info")) {
|
||||
returnResult = receiveTransportInfo(packet);
|
||||
} else if (packet.isAction("transport-replace")) {
|
||||
if (packet.getJingleContent().hasIbbTransport()) {
|
||||
returnResult = this.receiveFallbackToIbb(packet);
|
||||
} else {
|
||||
returnResult = false;
|
||||
Log.d(Config.LOGTAG, "trying to fallback to something unknown"
|
||||
+ packet.toString());
|
||||
}
|
||||
} else if (packet.isAction("transport-accept")) {
|
||||
returnResult = this.receiveTransportAccept(packet);
|
||||
} else {
|
||||
Log.d(Config.LOGTAG, "packet arrived in connection. action was "
|
||||
+ packet.getAction());
|
||||
returnResult = false;
|
||||
}
|
||||
IqPacket response;
|
||||
if (returnResult) {
|
||||
response = packet.generateRespone(IqPacket.TYPE_RESULT);
|
||||
|
||||
} else {
|
||||
response = packet.generateRespone(IqPacket.TYPE_ERROR);
|
||||
}
|
||||
account.getXmppConnection().sendIqPacket(response, null);
|
||||
}
|
||||
|
||||
public void init(Message message) {
|
||||
this.contentCreator = "initiator";
|
||||
this.contentName = this.mJingleConnectionManager.nextRandomId();
|
||||
this.message = message;
|
||||
this.account = message.getConversation().getAccount();
|
||||
this.initiator = this.account.getFullJid();
|
||||
this.responder = this.message.getCounterpart();
|
||||
this.sessionId = this.mJingleConnectionManager.nextRandomId();
|
||||
if (this.candidates.size() > 0) {
|
||||
this.sendInitRequest();
|
||||
} else {
|
||||
this.mJingleConnectionManager.getPrimaryCandidate(account,
|
||||
new OnPrimaryCandidateFound() {
|
||||
|
||||
@Override
|
||||
public void onPrimaryCandidateFound(boolean success,
|
||||
final JingleCandidate candidate) {
|
||||
if (success) {
|
||||
final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
|
||||
JingleConnection.this, candidate);
|
||||
connections.put(candidate.getCid(),
|
||||
socksConnection);
|
||||
socksConnection
|
||||
.connect(new OnTransportConnected() {
|
||||
|
||||
@Override
|
||||
public void failed() {
|
||||
Log.d(Config.LOGTAG,
|
||||
"connection to our own primary candidete failed");
|
||||
sendInitRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void established() {
|
||||
Log.d(Config.LOGTAG,
|
||||
"succesfully connected to our own primary candidate");
|
||||
mergeCandidate(candidate);
|
||||
sendInitRequest();
|
||||
}
|
||||
});
|
||||
mergeCandidate(candidate);
|
||||
} else {
|
||||
Log.d(Config.LOGTAG,
|
||||
"no primary candidate of our own was found");
|
||||
sendInitRequest();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void init(Account account, JinglePacket packet) {
|
||||
this.mJingleStatus = JINGLE_STATUS_INITIATED;
|
||||
Conversation conversation = this.mXmppConnectionService
|
||||
.findOrCreateConversation(account,
|
||||
packet.getFrom().split("/", 2)[0], false);
|
||||
this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
|
||||
this.message.setStatus(Message.STATUS_RECEIVED);
|
||||
this.message.setType(Message.TYPE_IMAGE);
|
||||
this.mStatus = Downloadable.STATUS_OFFER;
|
||||
this.message.setDownloadable(this);
|
||||
String[] fromParts = packet.getFrom().split("/", 2);
|
||||
this.message.setPresence(fromParts[1]);
|
||||
this.account = account;
|
||||
this.initiator = packet.getFrom();
|
||||
this.responder = this.account.getFullJid();
|
||||
this.sessionId = packet.getSessionId();
|
||||
Content content = packet.getJingleContent();
|
||||
this.contentCreator = content.getAttribute("creator");
|
||||
this.contentName = content.getAttribute("name");
|
||||
this.transportId = content.getTransportId();
|
||||
this.mergeCandidates(JingleCandidate.parse(content.socks5transport()
|
||||
.getChildren()));
|
||||
this.fileOffer = packet.getJingleContent().getFileOffer();
|
||||
if (fileOffer != null) {
|
||||
Element fileSize = fileOffer.findChild("size");
|
||||
Element fileNameElement = fileOffer.findChild("name");
|
||||
if (fileNameElement != null) {
|
||||
boolean supportedFile = false;
|
||||
String[] filename = fileNameElement.getContent()
|
||||
.toLowerCase(Locale.US).split("\\.");
|
||||
if (Arrays.asList(this.extensions).contains(
|
||||
filename[filename.length - 1])) {
|
||||
supportedFile = true;
|
||||
} else if (Arrays.asList(this.cryptoExtensions).contains(
|
||||
filename[filename.length - 1])) {
|
||||
if (filename.length == 3) {
|
||||
if (Arrays.asList(this.extensions).contains(
|
||||
filename[filename.length - 2])) {
|
||||
supportedFile = true;
|
||||
if (filename[filename.length - 1].equals("otr")) {
|
||||
Log.d(Config.LOGTAG, "receiving otr file");
|
||||
this.message
|
||||
.setEncryption(Message.ENCRYPTION_OTR);
|
||||
} else {
|
||||
this.message
|
||||
.setEncryption(Message.ENCRYPTION_PGP);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (supportedFile) {
|
||||
long size = Long.parseLong(fileSize.getContent());
|
||||
message.setBody(Long.toString(size));
|
||||
conversation.add(message);
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
if (size <= this.mJingleConnectionManager
|
||||
.getAutoAcceptFileSize()) {
|
||||
Log.d(Config.LOGTAG, "auto accepting file from "
|
||||
+ packet.getFrom());
|
||||
this.acceptedAutomatically = true;
|
||||
this.sendAccept();
|
||||
} else {
|
||||
message.markUnread();
|
||||
Log.d(Config.LOGTAG,
|
||||
"not auto accepting new file offer with size: "
|
||||
+ size
|
||||
+ " allowed size:"
|
||||
+ this.mJingleConnectionManager
|
||||
.getAutoAcceptFileSize());
|
||||
this.mXmppConnectionService.getNotificationService()
|
||||
.push(message);
|
||||
}
|
||||
this.file = this.mXmppConnectionService.getFileBackend()
|
||||
.getFile(message, false);
|
||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||
byte[] key = conversation.getSymmetricKey();
|
||||
if (key == null) {
|
||||
this.sendCancel();
|
||||
this.cancel();
|
||||
return;
|
||||
} else {
|
||||
this.file.setKey(key);
|
||||
}
|
||||
}
|
||||
this.file.setExpectedSize(size);
|
||||
} else {
|
||||
this.sendCancel();
|
||||
this.cancel();
|
||||
}
|
||||
} else {
|
||||
this.sendCancel();
|
||||
this.cancel();
|
||||
}
|
||||
} else {
|
||||
this.sendCancel();
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendInitRequest() {
|
||||
JinglePacket packet = this.bootstrapPacket("session-initiate");
|
||||
Content content = new Content(this.contentCreator, this.contentName);
|
||||
if (message.getType() == Message.TYPE_IMAGE) {
|
||||
content.setTransportId(this.transportId);
|
||||
this.file = this.mXmppConnectionService.getFileBackend().getFile(
|
||||
message, false);
|
||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||
Conversation conversation = this.message.getConversation();
|
||||
this.mXmppConnectionService.renewSymmetricKey(conversation);
|
||||
content.setFileOffer(this.file, true);
|
||||
this.file.setKey(conversation.getSymmetricKey());
|
||||
} else {
|
||||
content.setFileOffer(this.file, false);
|
||||
}
|
||||
this.transportId = this.mJingleConnectionManager.nextRandomId();
|
||||
content.setTransportId(this.transportId);
|
||||
content.socks5transport().setChildren(getCandidatesAsElements());
|
||||
packet.setContent(content);
|
||||
this.sendJinglePacket(packet);
|
||||
this.mJingleStatus = JINGLE_STATUS_INITIATED;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Element> getCandidatesAsElements() {
|
||||
List<Element> elements = new ArrayList<Element>();
|
||||
for (JingleCandidate c : this.candidates) {
|
||||
elements.add(c.toElement());
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
private void sendAccept() {
|
||||
mJingleStatus = JINGLE_STATUS_ACCEPTED;
|
||||
this.mStatus = Downloadable.STATUS_DOWNLOADING;
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
this.mJingleConnectionManager.getPrimaryCandidate(this.account,
|
||||
new OnPrimaryCandidateFound() {
|
||||
|
||||
@Override
|
||||
public void onPrimaryCandidateFound(boolean success,
|
||||
final JingleCandidate candidate) {
|
||||
final JinglePacket packet = bootstrapPacket("session-accept");
|
||||
final Content content = new Content(contentCreator,
|
||||
contentName);
|
||||
content.setFileOffer(fileOffer);
|
||||
content.setTransportId(transportId);
|
||||
if ((success) && (!equalCandidateExists(candidate))) {
|
||||
final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
|
||||
JingleConnection.this, candidate);
|
||||
connections.put(candidate.getCid(), socksConnection);
|
||||
socksConnection.connect(new OnTransportConnected() {
|
||||
|
||||
@Override
|
||||
public void failed() {
|
||||
Log.d(Config.LOGTAG,
|
||||
"connection to our own primary candidate failed");
|
||||
content.socks5transport().setChildren(
|
||||
getCandidatesAsElements());
|
||||
packet.setContent(content);
|
||||
sendJinglePacket(packet);
|
||||
connectNextCandidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void established() {
|
||||
Log.d(Config.LOGTAG,
|
||||
"connected to primary candidate");
|
||||
mergeCandidate(candidate);
|
||||
content.socks5transport().setChildren(
|
||||
getCandidatesAsElements());
|
||||
packet.setContent(content);
|
||||
sendJinglePacket(packet);
|
||||
connectNextCandidate();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.d(Config.LOGTAG,
|
||||
"did not find a primary candidate for ourself");
|
||||
content.socks5transport().setChildren(
|
||||
getCandidatesAsElements());
|
||||
packet.setContent(content);
|
||||
sendJinglePacket(packet);
|
||||
connectNextCandidate();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private JinglePacket bootstrapPacket(String action) {
|
||||
JinglePacket packet = new JinglePacket();
|
||||
packet.setAction(action);
|
||||
packet.setFrom(account.getFullJid());
|
||||
packet.setTo(this.message.getCounterpart());
|
||||
packet.setSessionId(this.sessionId);
|
||||
packet.setInitiator(this.initiator);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private void sendJinglePacket(JinglePacket packet) {
|
||||
// Log.d(Config.LOGTAG,packet.toString());
|
||||
account.getXmppConnection().sendIqPacket(packet, responseListener);
|
||||
}
|
||||
|
||||
private boolean receiveAccept(JinglePacket packet) {
|
||||
Content content = packet.getJingleContent();
|
||||
mergeCandidates(JingleCandidate.parse(content.socks5transport()
|
||||
.getChildren()));
|
||||
this.mJingleStatus = JINGLE_STATUS_ACCEPTED;
|
||||
mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
|
||||
this.connectNextCandidate();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean receiveTransportInfo(JinglePacket packet) {
|
||||
Content content = packet.getJingleContent();
|
||||
if (content.hasSocks5Transport()) {
|
||||
if (content.socks5transport().hasChild("activated")) {
|
||||
if ((this.transport != null)
|
||||
&& (this.transport instanceof JingleSocks5Transport)) {
|
||||
onProxyActivated.success();
|
||||
} else {
|
||||
String cid = content.socks5transport()
|
||||
.findChild("activated").getAttribute("cid");
|
||||
Log.d(Config.LOGTAG, "received proxy activated (" + cid
|
||||
+ ")prior to choosing our own transport");
|
||||
JingleSocks5Transport connection = this.connections
|
||||
.get(cid);
|
||||
if (connection != null) {
|
||||
connection.setActivated(true);
|
||||
} else {
|
||||
Log.d(Config.LOGTAG, "activated connection not found");
|
||||
this.sendCancel();
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (content.socks5transport().hasChild("proxy-error")) {
|
||||
onProxyActivated.failed();
|
||||
return true;
|
||||
} else if (content.socks5transport().hasChild("candidate-error")) {
|
||||
Log.d(Config.LOGTAG, "received candidate error");
|
||||
this.receivedCandidate = true;
|
||||
if ((mJingleStatus == JINGLE_STATUS_ACCEPTED)
|
||||
&& (this.sentCandidate)) {
|
||||
this.connect();
|
||||
}
|
||||
return true;
|
||||
} else if (content.socks5transport().hasChild("candidate-used")) {
|
||||
String cid = content.socks5transport()
|
||||
.findChild("candidate-used").getAttribute("cid");
|
||||
if (cid != null) {
|
||||
Log.d(Config.LOGTAG, "candidate used by counterpart:" + cid);
|
||||
JingleCandidate candidate = getCandidate(cid);
|
||||
candidate.flagAsUsedByCounterpart();
|
||||
this.receivedCandidate = true;
|
||||
if ((mJingleStatus == JINGLE_STATUS_ACCEPTED)
|
||||
&& (this.sentCandidate)) {
|
||||
this.connect();
|
||||
} else {
|
||||
Log.d(Config.LOGTAG,
|
||||
"ignoring because file is already in transmission or we havent sent our candidate yet");
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void connect() {
|
||||
final JingleSocks5Transport connection = chooseConnection();
|
||||
this.transport = connection;
|
||||
if (connection == null) {
|
||||
Log.d(Config.LOGTAG, "could not find suitable candidate");
|
||||
this.disconnect();
|
||||
if (this.initiator.equals(account.getFullJid())) {
|
||||
this.sendFallbackToIbb();
|
||||
}
|
||||
} else {
|
||||
this.mJingleStatus = JINGLE_STATUS_TRANSMITTING;
|
||||
if (connection.needsActivation()) {
|
||||
if (connection.getCandidate().isOurs()) {
|
||||
Log.d(Config.LOGTAG, "candidate "
|
||||
+ connection.getCandidate().getCid()
|
||||
+ " was our proxy. going to activate");
|
||||
IqPacket activation = new IqPacket(IqPacket.TYPE_SET);
|
||||
activation.setTo(connection.getCandidate().getJid());
|
||||
activation.query("http://jabber.org/protocol/bytestreams")
|
||||
.setAttribute("sid", this.getSessionId());
|
||||
activation.query().addChild("activate")
|
||||
.setContent(this.getCounterPart());
|
||||
this.account.getXmppConnection().sendIqPacket(activation,
|
||||
new OnIqPacketReceived() {
|
||||
|
||||
@Override
|
||||
public void onIqPacketReceived(Account account,
|
||||
IqPacket packet) {
|
||||
if (packet.getType() == IqPacket.TYPE_ERROR) {
|
||||
onProxyActivated.failed();
|
||||
} else {
|
||||
onProxyActivated.success();
|
||||
sendProxyActivated(connection
|
||||
.getCandidate().getCid());
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.d(Config.LOGTAG,
|
||||
"candidate "
|
||||
+ connection.getCandidate().getCid()
|
||||
+ " was a proxy. waiting for other party to activate");
|
||||
}
|
||||
} else {
|
||||
if (initiator.equals(account.getFullJid())) {
|
||||
Log.d(Config.LOGTAG, "we were initiating. sending file");
|
||||
connection.send(file, onFileTransmissionSatusChanged);
|
||||
} else {
|
||||
Log.d(Config.LOGTAG, "we were responding. receiving file");
|
||||
connection.receive(file, onFileTransmissionSatusChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JingleSocks5Transport chooseConnection() {
|
||||
JingleSocks5Transport connection = null;
|
||||
for (Entry<String, JingleSocks5Transport> cursor : connections
|
||||
.entrySet()) {
|
||||
JingleSocks5Transport currentConnection = cursor.getValue();
|
||||
// Log.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString());
|
||||
if (currentConnection.isEstablished()
|
||||
&& (currentConnection.getCandidate().isUsedByCounterpart() || (!currentConnection
|
||||
.getCandidate().isOurs()))) {
|
||||
// Log.d(Config.LOGTAG,"is usable");
|
||||
if (connection == null) {
|
||||
connection = currentConnection;
|
||||
} else {
|
||||
if (connection.getCandidate().getPriority() < currentConnection
|
||||
.getCandidate().getPriority()) {
|
||||
connection = currentConnection;
|
||||
} else if (connection.getCandidate().getPriority() == currentConnection
|
||||
.getCandidate().getPriority()) {
|
||||
// Log.d(Config.LOGTAG,"found two candidates with same priority");
|
||||
if (initiator.equals(account.getFullJid())) {
|
||||
if (currentConnection.getCandidate().isOurs()) {
|
||||
connection = currentConnection;
|
||||
}
|
||||
} else {
|
||||
if (!currentConnection.getCandidate().isOurs()) {
|
||||
connection = currentConnection;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
private void sendSuccess() {
|
||||
JinglePacket packet = bootstrapPacket("session-terminate");
|
||||
Reason reason = new Reason();
|
||||
reason.addChild("success");
|
||||
packet.setReason(reason);
|
||||
this.sendJinglePacket(packet);
|
||||
this.disconnect();
|
||||
this.mJingleStatus = JINGLE_STATUS_FINISHED;
|
||||
this.message.setStatus(Message.STATUS_RECEIVED);
|
||||
this.message.setDownloadable(null);
|
||||
this.mXmppConnectionService.updateMessage(message);
|
||||
this.mJingleConnectionManager.finishConnection(this);
|
||||
}
|
||||
|
||||
private void sendFallbackToIbb() {
|
||||
Log.d(Config.LOGTAG, "sending fallback to ibb");
|
||||
JinglePacket packet = this.bootstrapPacket("transport-replace");
|
||||
Content content = new Content(this.contentCreator, this.contentName);
|
||||
this.transportId = this.mJingleConnectionManager.nextRandomId();
|
||||
content.setTransportId(this.transportId);
|
||||
content.ibbTransport().setAttribute("block-size",
|
||||
Integer.toString(this.ibbBlockSize));
|
||||
packet.setContent(content);
|
||||
this.sendJinglePacket(packet);
|
||||
}
|
||||
|
||||
private boolean receiveFallbackToIbb(JinglePacket packet) {
|
||||
Log.d(Config.LOGTAG, "receiving fallack to ibb");
|
||||
String receivedBlockSize = packet.getJingleContent().ibbTransport()
|
||||
.getAttribute("block-size");
|
||||
if (receivedBlockSize != null) {
|
||||
int bs = Integer.parseInt(receivedBlockSize);
|
||||
if (bs > this.ibbBlockSize) {
|
||||
this.ibbBlockSize = bs;
|
||||
}
|
||||
}
|
||||
this.transportId = packet.getJingleContent().getTransportId();
|
||||
this.transport = new JingleInbandTransport(this.account,
|
||||
this.responder, this.transportId, this.ibbBlockSize);
|
||||
this.transport.receive(file, onFileTransmissionSatusChanged);
|
||||
JinglePacket answer = bootstrapPacket("transport-accept");
|
||||
Content content = new Content("initiator", "a-file-offer");
|
||||
content.setTransportId(this.transportId);
|
||||
content.ibbTransport().setAttribute("block-size",
|
||||
Integer.toString(this.ibbBlockSize));
|
||||
answer.setContent(content);
|
||||
this.sendJinglePacket(answer);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean receiveTransportAccept(JinglePacket packet) {
|
||||
if (packet.getJingleContent().hasIbbTransport()) {
|
||||
String receivedBlockSize = packet.getJingleContent().ibbTransport()
|
||||
.getAttribute("block-size");
|
||||
if (receivedBlockSize != null) {
|
||||
int bs = Integer.parseInt(receivedBlockSize);
|
||||
if (bs > this.ibbBlockSize) {
|
||||
this.ibbBlockSize = bs;
|
||||
}
|
||||
}
|
||||
this.transport = new JingleInbandTransport(this.account,
|
||||
this.responder, this.transportId, this.ibbBlockSize);
|
||||
this.transport.connect(new OnTransportConnected() {
|
||||
|
||||
@Override
|
||||
public void failed() {
|
||||
Log.d(Config.LOGTAG, "ibb open failed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void established() {
|
||||
JingleConnection.this.transport.send(file,
|
||||
onFileTransmissionSatusChanged);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveSuccess() {
|
||||
this.mJingleStatus = JINGLE_STATUS_FINISHED;
|
||||
this.mXmppConnectionService.markMessage(this.message,
|
||||
Message.STATUS_SEND);
|
||||
this.disconnect();
|
||||
this.mJingleConnectionManager.finishConnection(this);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
this.mJingleStatus = JINGLE_STATUS_CANCELED;
|
||||
this.disconnect();
|
||||
if (this.message != null) {
|
||||
if (this.responder.equals(account.getFullJid())) {
|
||||
this.mStatus = Downloadable.STATUS_FAILED;
|
||||
this.mXmppConnectionService.updateConversationUi();
|
||||
} else {
|
||||
if (this.mJingleStatus == JINGLE_STATUS_INITIATED) {
|
||||
this.mXmppConnectionService.markMessage(this.message,
|
||||
Message.STATUS_SEND_REJECTED);
|
||||
} else {
|
||||
this.mXmppConnectionService.markMessage(this.message,
|
||||
Message.STATUS_SEND_FAILED);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.mJingleConnectionManager.finishConnection(this);
|
||||
}
|
||||
|
||||
private void sendCancel() {
|
||||
JinglePacket packet = bootstrapPacket("session-terminate");
|
||||
Reason reason = new Reason();
|
||||
reason.addChild("cancel");
|
||||
packet.setReason(reason);
|
||||
this.sendJinglePacket(packet);
|
||||
}
|
||||
|
||||
private void connectNextCandidate() {
|
||||
for (JingleCandidate candidate : this.candidates) {
|
||||
if ((!connections.containsKey(candidate.getCid()) && (!candidate
|
||||
.isOurs()))) {
|
||||
this.connectWithCandidate(candidate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.sendCandidateError();
|
||||
}
|
||||
|
||||
private void connectWithCandidate(final JingleCandidate candidate) {
|
||||
final JingleSocks5Transport socksConnection = new JingleSocks5Transport(
|
||||
this, candidate);
|
||||
connections.put(candidate.getCid(), socksConnection);
|
||||
socksConnection.connect(new OnTransportConnected() {
|
||||
|
||||
@Override
|
||||
public void failed() {
|
||||
Log.d(Config.LOGTAG,
|
||||
"connection failed with " + candidate.getHost() + ":"
|
||||
+ candidate.getPort());
|
||||
connectNextCandidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void established() {
|
||||
Log.d(Config.LOGTAG,
|
||||
"established connection with " + candidate.getHost()
|
||||
+ ":" + candidate.getPort());
|
||||
sendCandidateUsed(candidate.getCid());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void disconnect() {
|
||||
Iterator<Entry<String, JingleSocks5Transport>> it = this.connections
|
||||
.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Entry<String, JingleSocks5Transport> pairs = it.next();
|
||||
pairs.getValue().disconnect();
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendProxyActivated(String cid) {
|
||||
JinglePacket packet = bootstrapPacket("transport-info");
|
||||
Content content = new Content(this.contentCreator, this.contentName);
|
||||
content.setTransportId(this.transportId);
|
||||
content.socks5transport().addChild("activated")
|
||||
.setAttribute("cid", cid);
|
||||
packet.setContent(content);
|
||||
this.sendJinglePacket(packet);
|
||||
}
|
||||
|
||||
private void sendCandidateUsed(final String cid) {
|
||||
JinglePacket packet = bootstrapPacket("transport-info");
|
||||
Content content = new Content(this.contentCreator, this.contentName);
|
||||
content.setTransportId(this.transportId);
|
||||
content.socks5transport().addChild("candidate-used")
|
||||
.setAttribute("cid", cid);
|
||||
packet.setContent(content);
|
||||
this.sentCandidate = true;
|
||||
if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
|
||||
connect();
|
||||
}
|
||||
this.sendJinglePacket(packet);
|
||||
}
|
||||
|
||||
private void sendCandidateError() {
|
||||
JinglePacket packet = bootstrapPacket("transport-info");
|
||||
Content content = new Content(this.contentCreator, this.contentName);
|
||||
content.setTransportId(this.transportId);
|
||||
content.socks5transport().addChild("candidate-error");
|
||||
packet.setContent(content);
|
||||
this.sentCandidate = true;
|
||||
if ((receivedCandidate) && (mJingleStatus == JINGLE_STATUS_ACCEPTED)) {
|
||||
connect();
|
||||
}
|
||||
this.sendJinglePacket(packet);
|
||||
}
|
||||
|
||||
public String getInitiator() {
|
||||
return this.initiator;
|
||||
}
|
||||
|
||||
public String getResponder() {
|
||||
return this.responder;
|
||||
}
|
||||
|
||||
public int getJingleStatus() {
|
||||
return this.mJingleStatus;
|
||||
}
|
||||
|
||||
private boolean equalCandidateExists(JingleCandidate candidate) {
|
||||
for (JingleCandidate c : this.candidates) {
|
||||
if (c.equalValues(candidate)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void mergeCandidate(JingleCandidate candidate) {
|
||||
for (JingleCandidate c : this.candidates) {
|
||||
if (c.equals(candidate)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.candidates.add(candidate);
|
||||
}
|
||||
|
||||
private void mergeCandidates(List<JingleCandidate> candidates) {
|
||||
for (JingleCandidate c : candidates) {
|
||||
mergeCandidate(c);
|
||||
}
|
||||
}
|
||||
|
||||
private JingleCandidate getCandidate(String cid) {
|
||||
for (JingleCandidate c : this.candidates) {
|
||||
if (c.getCid().equals(cid)) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
interface OnProxyActivated {
|
||||
public void success();
|
||||
|
||||
public void failed();
|
||||
}
|
||||
|
||||
public boolean hasTransportId(String sid) {
|
||||
return sid.equals(this.transportId);
|
||||
}
|
||||
|
||||
public JingleTransport getTransport() {
|
||||
return this.transport;
|
||||
}
|
||||
|
||||
public boolean start() {
|
||||
if (account.getStatus() == Account.STATUS_ONLINE) {
|
||||
if (mJingleStatus == JINGLE_STATUS_INITIATED) {
|
||||
new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
sendAccept();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatus() {
|
||||
return this.mStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFileSize() {
|
||||
if (this.file != null) {
|
||||
return this.file.getExpectedSize();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.util.Log;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.services.AbstractConnectionManager;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
|
||||
public class JingleConnectionManager extends AbstractConnectionManager {
|
||||
private List<JingleConnection> connections = new CopyOnWriteArrayList<JingleConnection>();
|
||||
|
||||
private HashMap<String, JingleCandidate> primaryCandidates = new HashMap<String, JingleCandidate>();
|
||||
|
||||
@SuppressLint("TrulyRandom")
|
||||
private SecureRandom random = new SecureRandom();
|
||||
|
||||
public JingleConnectionManager(XmppConnectionService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
public void deliverPacket(Account account, JinglePacket packet) {
|
||||
if (packet.isAction("session-initiate")) {
|
||||
JingleConnection connection = new JingleConnection(this);
|
||||
connection.init(account, packet);
|
||||
connections.add(connection);
|
||||
} else {
|
||||
for (JingleConnection connection : connections) {
|
||||
if (connection.getAccount() == account
|
||||
&& connection.getSessionId().equals(
|
||||
packet.getSessionId())
|
||||
&& connection.getCounterPart().equals(packet.getFrom())) {
|
||||
connection.deliverPacket(packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
account.getXmppConnection().sendIqPacket(
|
||||
packet.generateRespone(IqPacket.TYPE_ERROR), null);
|
||||
}
|
||||
}
|
||||
|
||||
public JingleConnection createNewConnection(Message message) {
|
||||
JingleConnection connection = new JingleConnection(this);
|
||||
connection.init(message);
|
||||
this.connections.add(connection);
|
||||
return connection;
|
||||
}
|
||||
|
||||
public JingleConnection createNewConnection(JinglePacket packet) {
|
||||
JingleConnection connection = new JingleConnection(this);
|
||||
this.connections.add(connection);
|
||||
return connection;
|
||||
}
|
||||
|
||||
public void finishConnection(JingleConnection connection) {
|
||||
this.connections.remove(connection);
|
||||
}
|
||||
|
||||
public void getPrimaryCandidate(Account account,
|
||||
final OnPrimaryCandidateFound listener) {
|
||||
if (!this.primaryCandidates.containsKey(account.getJid())) {
|
||||
String xmlns = "http://jabber.org/protocol/bytestreams";
|
||||
final String proxy = account.getXmppConnection()
|
||||
.findDiscoItemByFeature(xmlns);
|
||||
if (proxy != null) {
|
||||
IqPacket iq = new IqPacket(IqPacket.TYPE_GET);
|
||||
iq.setTo(proxy);
|
||||
iq.query(xmlns);
|
||||
account.getXmppConnection().sendIqPacket(iq,
|
||||
new OnIqPacketReceived() {
|
||||
|
||||
@Override
|
||||
public void onIqPacketReceived(Account account,
|
||||
IqPacket packet) {
|
||||
Element streamhost = packet
|
||||
.query()
|
||||
.findChild("streamhost",
|
||||
"http://jabber.org/protocol/bytestreams");
|
||||
if (streamhost != null) {
|
||||
JingleCandidate candidate = new JingleCandidate(
|
||||
nextRandomId(), true);
|
||||
candidate.setHost(streamhost
|
||||
.getAttribute("host"));
|
||||
candidate.setPort(Integer
|
||||
.parseInt(streamhost
|
||||
.getAttribute("port")));
|
||||
candidate
|
||||
.setType(JingleCandidate.TYPE_PROXY);
|
||||
candidate.setJid(proxy);
|
||||
candidate.setPriority(655360 + 65535);
|
||||
primaryCandidates.put(account.getJid(),
|
||||
candidate);
|
||||
listener.onPrimaryCandidateFound(true,
|
||||
candidate);
|
||||
} else {
|
||||
listener.onPrimaryCandidateFound(false,
|
||||
null);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
listener.onPrimaryCandidateFound(false, null);
|
||||
}
|
||||
|
||||
} else {
|
||||
listener.onPrimaryCandidateFound(true,
|
||||
this.primaryCandidates.get(account.getJid()));
|
||||
}
|
||||
}
|
||||
|
||||
public String nextRandomId() {
|
||||
return new BigInteger(50, random).toString(32);
|
||||
}
|
||||
|
||||
public void deliverIbbPacket(Account account, IqPacket packet) {
|
||||
String sid = null;
|
||||
Element payload = null;
|
||||
if (packet.hasChild("open", "http://jabber.org/protocol/ibb")) {
|
||||
payload = packet
|
||||
.findChild("open", "http://jabber.org/protocol/ibb");
|
||||
sid = payload.getAttribute("sid");
|
||||
} else if (packet.hasChild("data", "http://jabber.org/protocol/ibb")) {
|
||||
payload = packet
|
||||
.findChild("data", "http://jabber.org/protocol/ibb");
|
||||
sid = payload.getAttribute("sid");
|
||||
}
|
||||
if (sid != null) {
|
||||
for (JingleConnection connection : connections) {
|
||||
if (connection.getAccount() == account
|
||||
&& connection.hasTransportId(sid)) {
|
||||
JingleTransport transport = connection.getTransport();
|
||||
if (transport instanceof JingleInbandTransport) {
|
||||
JingleInbandTransport inbandTransport = (JingleInbandTransport) transport;
|
||||
inbandTransport.deliverPayload(packet, payload);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.d(Config.LOGTAG,
|
||||
"couldnt deliver payload: " + payload.toString());
|
||||
} else {
|
||||
Log.d(Config.LOGTAG, "no sid found in incomming ibb packet");
|
||||
}
|
||||
}
|
||||
|
||||
public void cancelInTransmission() {
|
||||
for (JingleConnection connection : this.connections) {
|
||||
if (connection.getJingleStatus() == JingleConnection.JINGLE_STATUS_TRANSMITTING) {
|
||||
connection.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import android.util.Base64;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
|
||||
public class JingleInbandTransport extends JingleTransport {
|
||||
|
||||
private Account account;
|
||||
private String counterpart;
|
||||
private int blockSize;
|
||||
private int bufferSize;
|
||||
private int seq = 0;
|
||||
private String sessionId;
|
||||
|
||||
private boolean established = false;
|
||||
|
||||
private DownloadableFile file;
|
||||
|
||||
private InputStream fileInputStream = null;
|
||||
private OutputStream fileOutputStream;
|
||||
private long remainingSize;
|
||||
private MessageDigest digest;
|
||||
|
||||
private OnFileTransmissionStatusChanged onFileTransmissionStatusChanged;
|
||||
|
||||
private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() {
|
||||
@Override
|
||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
||||
if (packet.getType() == IqPacket.TYPE_RESULT) {
|
||||
sendNextBlock();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public JingleInbandTransport(Account account, String counterpart,
|
||||
String sid, int blocksize) {
|
||||
this.account = account;
|
||||
this.counterpart = counterpart;
|
||||
this.blockSize = blocksize;
|
||||
this.bufferSize = blocksize / 4;
|
||||
this.sessionId = sid;
|
||||
}
|
||||
|
||||
public void connect(final OnTransportConnected callback) {
|
||||
IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
|
||||
iq.setTo(this.counterpart);
|
||||
Element open = iq.addChild("open", "http://jabber.org/protocol/ibb");
|
||||
open.setAttribute("sid", this.sessionId);
|
||||
open.setAttribute("stanza", "iq");
|
||||
open.setAttribute("block-size", Integer.toString(this.blockSize));
|
||||
|
||||
this.account.getXmppConnection().sendIqPacket(iq,
|
||||
new OnIqPacketReceived() {
|
||||
|
||||
@Override
|
||||
public void onIqPacketReceived(Account account,
|
||||
IqPacket packet) {
|
||||
if (packet.getType() == IqPacket.TYPE_ERROR) {
|
||||
callback.failed();
|
||||
} else {
|
||||
callback.established();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(DownloadableFile file,
|
||||
OnFileTransmissionStatusChanged callback) {
|
||||
this.onFileTransmissionStatusChanged = callback;
|
||||
this.file = file;
|
||||
try {
|
||||
this.digest = MessageDigest.getInstance("SHA-1");
|
||||
digest.reset();
|
||||
file.getParentFile().mkdirs();
|
||||
file.createNewFile();
|
||||
this.fileOutputStream = file.createOutputStream();
|
||||
if (this.fileOutputStream == null) {
|
||||
callback.onFileTransferAborted();
|
||||
return;
|
||||
}
|
||||
this.remainingSize = file.getExpectedSize();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
callback.onFileTransferAborted();
|
||||
} catch (IOException e) {
|
||||
callback.onFileTransferAborted();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(DownloadableFile file,
|
||||
OnFileTransmissionStatusChanged callback) {
|
||||
this.onFileTransmissionStatusChanged = callback;
|
||||
this.file = file;
|
||||
try {
|
||||
this.digest = MessageDigest.getInstance("SHA-1");
|
||||
this.digest.reset();
|
||||
fileInputStream = this.file.createInputStream();
|
||||
if (fileInputStream == null) {
|
||||
callback.onFileTransferAborted();
|
||||
return;
|
||||
}
|
||||
this.sendNextBlock();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
callback.onFileTransferAborted();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendNextBlock() {
|
||||
byte[] buffer = new byte[this.bufferSize];
|
||||
try {
|
||||
int count = fileInputStream.read(buffer);
|
||||
if (count == -1) {
|
||||
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
|
||||
fileInputStream.close();
|
||||
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
|
||||
} else {
|
||||
this.digest.update(buffer);
|
||||
String base64 = Base64.encodeToString(buffer, Base64.NO_WRAP);
|
||||
IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
|
||||
iq.setTo(this.counterpart);
|
||||
Element data = iq.addChild("data",
|
||||
"http://jabber.org/protocol/ibb");
|
||||
data.setAttribute("seq", Integer.toString(this.seq));
|
||||
data.setAttribute("block-size",
|
||||
Integer.toString(this.blockSize));
|
||||
data.setAttribute("sid", this.sessionId);
|
||||
data.setContent(base64);
|
||||
this.account.getXmppConnection().sendIqPacket(iq,
|
||||
this.onAckReceived);
|
||||
this.seq++;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
this.onFileTransmissionStatusChanged.onFileTransferAborted();
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveNextBlock(String data) {
|
||||
try {
|
||||
byte[] buffer = Base64.decode(data, Base64.NO_WRAP);
|
||||
if (this.remainingSize < buffer.length) {
|
||||
buffer = Arrays
|
||||
.copyOfRange(buffer, 0, (int) this.remainingSize);
|
||||
}
|
||||
this.remainingSize -= buffer.length;
|
||||
|
||||
this.fileOutputStream.write(buffer);
|
||||
|
||||
this.digest.update(buffer);
|
||||
if (this.remainingSize <= 0) {
|
||||
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
|
||||
fileOutputStream.flush();
|
||||
fileOutputStream.close();
|
||||
this.onFileTransmissionStatusChanged.onFileTransmitted(file);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
this.onFileTransmissionStatusChanged.onFileTransferAborted();
|
||||
}
|
||||
}
|
||||
|
||||
public void deliverPayload(IqPacket packet, Element payload) {
|
||||
if (payload.getName().equals("open")) {
|
||||
if (!established) {
|
||||
established = true;
|
||||
this.account.getXmppConnection().sendIqPacket(
|
||||
packet.generateRespone(IqPacket.TYPE_RESULT), null);
|
||||
} else {
|
||||
this.account.getXmppConnection().sendIqPacket(
|
||||
packet.generateRespone(IqPacket.TYPE_ERROR), null);
|
||||
}
|
||||
} else if (payload.getName().equals("data")) {
|
||||
this.receiveNextBlock(payload.getContent());
|
||||
this.account.getXmppConnection().sendIqPacket(
|
||||
packet.generateRespone(IqPacket.TYPE_RESULT), null);
|
||||
} else {
|
||||
// TODO some sort of exception
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
|
||||
public class JingleSocks5Transport extends JingleTransport {
|
||||
private JingleCandidate candidate;
|
||||
private String destination;
|
||||
private OutputStream outputStream;
|
||||
private InputStream inputStream;
|
||||
private boolean isEstablished = false;
|
||||
private boolean activated = false;
|
||||
protected Socket socket;
|
||||
|
||||
public JingleSocks5Transport(JingleConnection jingleConnection,
|
||||
JingleCandidate candidate) {
|
||||
this.candidate = candidate;
|
||||
try {
|
||||
MessageDigest mDigest = MessageDigest.getInstance("SHA-1");
|
||||
StringBuilder destBuilder = new StringBuilder();
|
||||
destBuilder.append(jingleConnection.getSessionId());
|
||||
if (candidate.isOurs()) {
|
||||
destBuilder.append(jingleConnection.getAccount().getFullJid());
|
||||
destBuilder.append(jingleConnection.getCounterPart());
|
||||
} else {
|
||||
destBuilder.append(jingleConnection.getCounterPart());
|
||||
destBuilder.append(jingleConnection.getAccount().getFullJid());
|
||||
}
|
||||
mDigest.reset();
|
||||
this.destination = CryptoHelper.bytesToHex(mDigest
|
||||
.digest(destBuilder.toString().getBytes()));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void connect(final OnTransportConnected callback) {
|
||||
new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
socket = new Socket(candidate.getHost(),
|
||||
candidate.getPort());
|
||||
inputStream = socket.getInputStream();
|
||||
outputStream = socket.getOutputStream();
|
||||
byte[] login = { 0x05, 0x01, 0x00 };
|
||||
byte[] expectedReply = { 0x05, 0x00 };
|
||||
byte[] reply = new byte[2];
|
||||
outputStream.write(login);
|
||||
inputStream.read(reply);
|
||||
final String connect = Character.toString('\u0005')
|
||||
+ '\u0001' + '\u0000' + '\u0003' + '\u0028'
|
||||
+ destination + '\u0000' + '\u0000';
|
||||
if (Arrays.equals(reply, expectedReply)) {
|
||||
outputStream.write(connect.getBytes());
|
||||
byte[] result = new byte[2];
|
||||
inputStream.read(result);
|
||||
int status = result[1];
|
||||
if (status == 0) {
|
||||
isEstablished = true;
|
||||
callback.established();
|
||||
} else {
|
||||
callback.failed();
|
||||
}
|
||||
} else {
|
||||
socket.close();
|
||||
callback.failed();
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
callback.failed();
|
||||
} catch (IOException e) {
|
||||
callback.failed();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
public void send(final DownloadableFile file,
|
||||
final OnFileTransmissionStatusChanged callback) {
|
||||
new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
InputStream fileInputStream = null;
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
digest.reset();
|
||||
fileInputStream = file.createInputStream();
|
||||
if (fileInputStream == null) {
|
||||
callback.onFileTransferAborted();
|
||||
return;
|
||||
}
|
||||
int count;
|
||||
byte[] buffer = new byte[8192];
|
||||
while ((count = fileInputStream.read(buffer)) > 0) {
|
||||
outputStream.write(buffer, 0, count);
|
||||
digest.update(buffer, 0, count);
|
||||
}
|
||||
outputStream.flush();
|
||||
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
|
||||
if (callback != null) {
|
||||
callback.onFileTransmitted(file);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
callback.onFileTransferAborted();
|
||||
} catch (IOException e) {
|
||||
callback.onFileTransferAborted();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
callback.onFileTransferAborted();
|
||||
} finally {
|
||||
try {
|
||||
if (fileInputStream != null) {
|
||||
fileInputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
callback.onFileTransferAborted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
public void receive(final DownloadableFile file,
|
||||
final OnFileTransmissionStatusChanged callback) {
|
||||
new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
digest.reset();
|
||||
inputStream.skip(45);
|
||||
socket.setSoTimeout(30000);
|
||||
file.getParentFile().mkdirs();
|
||||
file.createNewFile();
|
||||
OutputStream fileOutputStream = file.createOutputStream();
|
||||
if (fileOutputStream == null) {
|
||||
callback.onFileTransferAborted();
|
||||
return;
|
||||
}
|
||||
long remainingSize = file.getExpectedSize();
|
||||
byte[] buffer = new byte[8192];
|
||||
int count = buffer.length;
|
||||
while (remainingSize > 0) {
|
||||
count = inputStream.read(buffer);
|
||||
if (count == -1) {
|
||||
callback.onFileTransferAborted();
|
||||
return;
|
||||
} else {
|
||||
fileOutputStream.write(buffer, 0, count);
|
||||
digest.update(buffer, 0, count);
|
||||
remainingSize -= count;
|
||||
}
|
||||
}
|
||||
fileOutputStream.flush();
|
||||
fileOutputStream.close();
|
||||
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
|
||||
callback.onFileTransmitted(file);
|
||||
} catch (FileNotFoundException e) {
|
||||
callback.onFileTransferAborted();
|
||||
} catch (IOException e) {
|
||||
callback.onFileTransferAborted();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
callback.onFileTransferAborted();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public boolean isProxy() {
|
||||
return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
|
||||
}
|
||||
|
||||
public boolean needsActivation() {
|
||||
return (this.isProxy() && !this.activated);
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
if (this.socket != null) {
|
||||
try {
|
||||
this.socket.close();
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEstablished() {
|
||||
return this.isEstablished;
|
||||
}
|
||||
|
||||
public JingleCandidate getCandidate() {
|
||||
return this.candidate;
|
||||
}
|
||||
|
||||
public void setActivated(boolean activated) {
|
||||
this.activated = activated;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
|
||||
public abstract class JingleTransport {
|
||||
public abstract void connect(final OnTransportConnected callback);
|
||||
|
||||
public abstract void receive(final DownloadableFile file,
|
||||
final OnFileTransmissionStatusChanged callback);
|
||||
|
||||
public abstract void send(final DownloadableFile file,
|
||||
final OnFileTransmissionStatusChanged callback);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
|
||||
public interface OnFileTransmissionStatusChanged {
|
||||
public void onFileTransmitted(DownloadableFile file);
|
||||
|
||||
public void onFileTransferAborted();
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.xmpp.PacketReceived;
|
||||
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
|
||||
|
||||
public interface OnJinglePacketReceived extends PacketReceived {
|
||||
public void onJinglePacketReceived(Account account, JinglePacket packet);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
public interface OnPrimaryCandidateFound {
|
||||
public void onPrimaryCandidateFound(boolean success,
|
||||
JingleCandidate canditate);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package eu.siacs.conversations.xmpp.jingle;
|
||||
|
||||
public interface OnTransportConnected {
|
||||
public void failed();
|
||||
|
||||
public void established();
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package eu.siacs.conversations.xmpp.jingle.stanzas;
|
||||
|
||||
import eu.siacs.conversations.entities.DownloadableFile;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
|
||||
public class Content extends Element {
|
||||
|
||||
private String transportId;
|
||||
|
||||
private Content(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public Content() {
|
||||
super("content");
|
||||
}
|
||||
|
||||
public Content(String creator, String name) {
|
||||
super("content");
|
||||
this.setAttribute("creator", creator);
|
||||
this.setAttribute("name", name);
|
||||
}
|
||||
|
||||
public void setTransportId(String sid) {
|
||||
this.transportId = sid;
|
||||
}
|
||||
|
||||
public void setFileOffer(DownloadableFile actualFile, boolean otr) {
|
||||
Element description = this.addChild("description",
|
||||
"urn:xmpp:jingle:apps:file-transfer:3");
|
||||
Element offer = description.addChild("offer");
|
||||
Element file = offer.addChild("file");
|
||||
file.addChild("size").setContent(Long.toString(actualFile.getSize()));
|
||||
if (otr) {
|
||||
file.addChild("name").setContent(actualFile.getName() + ".otr");
|
||||
} else {
|
||||
file.addChild("name").setContent(actualFile.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public Element getFileOffer() {
|
||||
Element description = this.findChild("description",
|
||||
"urn:xmpp:jingle:apps:file-transfer:3");
|
||||
if (description == null) {
|
||||
return null;
|
||||
}
|
||||
Element offer = description.findChild("offer");
|
||||
if (offer == null) {
|
||||
return null;
|
||||
}
|
||||
return offer.findChild("file");
|
||||
}
|
||||
|
||||
public void setFileOffer(Element fileOffer) {
|
||||
Element description = this.findChild("description",
|
||||
"urn:xmpp:jingle:apps:file-transfer:3");
|
||||
if (description == null) {
|
||||
description = this.addChild("description",
|
||||
"urn:xmpp:jingle:apps:file-transfer:3");
|
||||
}
|
||||
description.addChild(fileOffer);
|
||||
}
|
||||
|
||||
public String getTransportId() {
|
||||
if (hasSocks5Transport()) {
|
||||
this.transportId = socks5transport().getAttribute("sid");
|
||||
} else if (hasIbbTransport()) {
|
||||
this.transportId = ibbTransport().getAttribute("sid");
|
||||
}
|
||||
return this.transportId;
|
||||
}
|
||||
|
||||
public Element socks5transport() {
|
||||
Element transport = this.findChild("transport",
|
||||
"urn:xmpp:jingle:transports:s5b:1");
|
||||
if (transport == null) {
|
||||
transport = this.addChild("transport",
|
||||
"urn:xmpp:jingle:transports:s5b:1");
|
||||
transport.setAttribute("sid", this.transportId);
|
||||
}
|
||||
return transport;
|
||||
}
|
||||
|
||||
public Element ibbTransport() {
|
||||
Element transport = this.findChild("transport",
|
||||
"urn:xmpp:jingle:transports:ibb:1");
|
||||
if (transport == null) {
|
||||
transport = this.addChild("transport",
|
||||
"urn:xmpp:jingle:transports:ibb:1");
|
||||
transport.setAttribute("sid", this.transportId);
|
||||
}
|
||||
return transport;
|
||||
}
|
||||
|
||||
public boolean hasSocks5Transport() {
|
||||
return this.hasChild("transport", "urn:xmpp:jingle:transports:s5b:1");
|
||||
}
|
||||
|
||||
public boolean hasIbbTransport() {
|
||||
return this.hasChild("transport", "urn:xmpp:jingle:transports:ibb:1");
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package eu.siacs.conversations.xmpp.jingle.stanzas;
|
||||
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
|
||||
public class JinglePacket extends IqPacket {
|
||||
Content content = null;
|
||||
Reason reason = null;
|
||||
Element jingle = new Element("jingle");
|
||||
|
||||
@Override
|
||||
public Element addChild(Element child) {
|
||||
if ("jingle".equals(child.getName())) {
|
||||
Element contentElement = child.findChild("content");
|
||||
if (contentElement != null) {
|
||||
this.content = new Content();
|
||||
this.content.setChildren(contentElement.getChildren());
|
||||
this.content.setAttributes(contentElement.getAttributes());
|
||||
}
|
||||
Element reasonElement = child.findChild("reason");
|
||||
if (reasonElement != null) {
|
||||
this.reason = new Reason();
|
||||
this.reason.setChildren(reasonElement.getChildren());
|
||||
this.reason.setAttributes(reasonElement.getAttributes());
|
||||
}
|
||||
this.jingle.setAttributes(child.getAttributes());
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
public JinglePacket setContent(Content content) {
|
||||
this.content = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Content getJingleContent() {
|
||||
if (this.content == null) {
|
||||
this.content = new Content();
|
||||
}
|
||||
return this.content;
|
||||
}
|
||||
|
||||
public JinglePacket setReason(Reason reason) {
|
||||
this.reason = reason;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Reason getReason() {
|
||||
return this.reason;
|
||||
}
|
||||
|
||||
private void build() {
|
||||
this.children.clear();
|
||||
this.jingle.clearChildren();
|
||||
this.jingle.setAttribute("xmlns", "urn:xmpp:jingle:1");
|
||||
if (this.content != null) {
|
||||
jingle.addChild(this.content);
|
||||
}
|
||||
if (this.reason != null) {
|
||||
jingle.addChild(this.reason);
|
||||
}
|
||||
this.children.add(jingle);
|
||||
this.setAttribute("type", "set");
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return this.jingle.getAttribute("sid");
|
||||
}
|
||||
|
||||
public void setSessionId(String sid) {
|
||||
this.jingle.setAttribute("sid", sid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
this.build();
|
||||
return super.toString();
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
this.jingle.setAttribute("action", action);
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return this.jingle.getAttribute("action");
|
||||
}
|
||||
|
||||
public void setInitiator(String initiator) {
|
||||
this.jingle.setAttribute("initiator", initiator);
|
||||
}
|
||||
|
||||
public boolean isAction(String action) {
|
||||
return action.equalsIgnoreCase(this.getAction());
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package eu.siacs.conversations.xmpp.jingle.stanzas;
|
||||
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
|
||||
public class Reason extends Element {
|
||||
private Reason(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public Reason() {
|
||||
super("reason");
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package eu.siacs.conversations.xmpp.pep;
|
||||
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import android.util.Base64;
|
||||
|
||||
public class Avatar {
|
||||
public String type;
|
||||
public String sha1sum;
|
||||
public String image;
|
||||
public int height;
|
||||
public int width;
|
||||
public long size;
|
||||
public String owner;
|
||||
|
||||
public byte[] getImageAsBytes() {
|
||||
return Base64.decode(image, Base64.DEFAULT);
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
if (type == null) {
|
||||
return sha1sum;
|
||||
} else if (type.equalsIgnoreCase("image/webp")) {
|
||||
return sha1sum + ".webp";
|
||||
} else if (type.equalsIgnoreCase("image/png")) {
|
||||
return sha1sum + ".png";
|
||||
} else {
|
||||
return sha1sum;
|
||||
}
|
||||
}
|
||||
|
||||
public static Avatar parseMetadata(Element items) {
|
||||
Element item = items.findChild("item");
|
||||
if (item == null) {
|
||||
return null;
|
||||
}
|
||||
Element metadata = item.findChild("metadata");
|
||||
if (metadata == null) {
|
||||
return null;
|
||||
}
|
||||
String primaryId = item.getAttribute("id");
|
||||
if (primaryId == null) {
|
||||
return null;
|
||||
}
|
||||
for (Element child : metadata.getChildren()) {
|
||||
if (child.getName().equals("info")
|
||||
&& primaryId.equals(child.getAttribute("id"))) {
|
||||
Avatar avatar = new Avatar();
|
||||
String height = child.getAttribute("height");
|
||||
String width = child.getAttribute("width");
|
||||
String size = child.getAttribute("bytes");
|
||||
try {
|
||||
if (height != null) {
|
||||
avatar.height = Integer.parseInt(height);
|
||||
}
|
||||
if (width != null) {
|
||||
avatar.width = Integer.parseInt(width);
|
||||
}
|
||||
if (size != null) {
|
||||
avatar.size = Long.parseLong(size);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
avatar.type = child.getAttribute("type");
|
||||
avatar.sha1sum = child.getAttribute("id");
|
||||
return avatar;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package eu.siacs.conversations.xmpp.stanzas;
|
||||
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
|
||||
public class AbstractStanza extends Element {
|
||||
|
||||
protected AbstractStanza(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public String getTo() {
|
||||
return getAttribute("to");
|
||||
}
|
||||
|
||||
public String getFrom() {
|
||||
return getAttribute("from");
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.getAttribute("id");
|
||||
}
|
||||
|
||||
public void setTo(String to) {
|
||||
setAttribute("to", to);
|
||||
}
|
||||
|
||||
public void setFrom(String from) {
|
||||
setAttribute("from", from);
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
setAttribute("id", id);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package eu.siacs.conversations.xmpp.stanzas;
|
||||
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
|
||||
public class IqPacket extends AbstractStanza {
|
||||
|
||||
public static final int TYPE_ERROR = -1;
|
||||
public static final int TYPE_SET = 0;
|
||||
public static final int TYPE_RESULT = 1;
|
||||
public static final int TYPE_GET = 2;
|
||||
|
||||
private IqPacket(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public IqPacket(int type) {
|
||||
super("iq");
|
||||
switch (type) {
|
||||
case TYPE_SET:
|
||||
this.setAttribute("type", "set");
|
||||
break;
|
||||
case TYPE_GET:
|
||||
this.setAttribute("type", "get");
|
||||
break;
|
||||
case TYPE_RESULT:
|
||||
this.setAttribute("type", "result");
|
||||
break;
|
||||
case TYPE_ERROR:
|
||||
this.setAttribute("type", "error");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public IqPacket() {
|
||||
super("iq");
|
||||
}
|
||||
|
||||
public Element query() {
|
||||
Element query = findChild("query");
|
||||
if (query == null) {
|
||||
query = addChild("query");
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
public Element query(String xmlns) {
|
||||
Element query = query();
|
||||
query.setAttribute("xmlns", xmlns);
|
||||
return query();
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
String type = getAttribute("type");
|
||||
if ("error".equals(type)) {
|
||||
return TYPE_ERROR;
|
||||
} else if ("result".equals(type)) {
|
||||
return TYPE_RESULT;
|
||||
} else if ("set".equals(type)) {
|
||||
return TYPE_SET;
|
||||
} else if ("get".equals(type)) {
|
||||
return TYPE_GET;
|
||||
} else {
|
||||
return 1000;
|
||||
}
|
||||
}
|
||||
|
||||
public IqPacket generateRespone(int type) {
|
||||
IqPacket packet = new IqPacket(type);
|
||||
packet.setTo(this.getFrom());
|
||||
packet.setId(this.getId());
|
||||
return packet;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package eu.siacs.conversations.xmpp.stanzas;
|
||||
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
|
||||
public class MessagePacket extends AbstractStanza {
|
||||
public static final int TYPE_CHAT = 0;
|
||||
public static final int TYPE_NORMAL = 2;
|
||||
public static final int TYPE_GROUPCHAT = 3;
|
||||
public static final int TYPE_ERROR = 4;
|
||||
public static final int TYPE_HEADLINE = 5;
|
||||
|
||||
public MessagePacket() {
|
||||
super("message");
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
Element body = this.findChild("body");
|
||||
if (body != null) {
|
||||
return body.getContent();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setBody(String text) {
|
||||
this.children.remove(findChild("body"));
|
||||
Element body = new Element("body");
|
||||
body.setContent(text);
|
||||
this.children.add(body);
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
switch (type) {
|
||||
case TYPE_CHAT:
|
||||
this.setAttribute("type", "chat");
|
||||
break;
|
||||
case TYPE_GROUPCHAT:
|
||||
this.setAttribute("type", "groupchat");
|
||||
break;
|
||||
case TYPE_NORMAL:
|
||||
break;
|
||||
default:
|
||||
this.setAttribute("type", "chat");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
String type = getAttribute("type");
|
||||
if (type == null) {
|
||||
return TYPE_NORMAL;
|
||||
} else if (type.equals("normal")) {
|
||||
return TYPE_NORMAL;
|
||||
} else if (type.equals("chat")) {
|
||||
return TYPE_CHAT;
|
||||
} else if (type.equals("groupchat")) {
|
||||
return TYPE_GROUPCHAT;
|
||||
} else if (type.equals("error")) {
|
||||
return TYPE_ERROR;
|
||||
} else if (type.equals("headline")) {
|
||||
return TYPE_HEADLINE;
|
||||
} else {
|
||||
return TYPE_NORMAL;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user