diff options
author | lookshe <github@lookshe.org> | 2016-03-06 19:42:55 +0100 |
---|---|---|
committer | lookshe <github@lookshe.org> | 2016-03-06 19:42:55 +0100 |
commit | 3c400703e082a1b180b35d891b8fb3460c7d5b87 (patch) | |
tree | 28738dd90fc41b4ab71897f38d324828778ad2e3 /src | |
parent | 72114d732427266024cdd6e27cd8d1aa60afae2f (diff) | |
parent | f28d77dc42f6bac5a026e0b1c78562dee8de45ac (diff) |
Merge branch 'trz/rebase' into trz/rename
Diffstat (limited to 'src')
307 files changed, 20871 insertions, 5778 deletions
diff --git a/src/free/java/de/thedevstack/conversationsplus/services/PushManagementService.java b/src/free/java/de/thedevstack/conversationsplus/services/PushManagementService.java new file mode 100644 index 00000000..c11b6e98 --- /dev/null +++ b/src/free/java/de/thedevstack/conversationsplus/services/PushManagementService.java @@ -0,0 +1,24 @@ +package de.thedevstack.conversationsplus.services; + +import de.thedevstack.conversationsplus.entities.Account; + +public class PushManagementService { + + protected final XmppConnectionService mXmppConnectionService; + + public PushManagementService(XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + public void registerPushTokenOnServer(Account account) { + //stub implementation. only affects playstore flavor + } + + public boolean available(Account account) { + return false; + } + + public boolean isStub() { + return true; + } +} diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 5ef50e48..04f1bc33 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -5,16 +5,21 @@ xmlns:tools="http://schemas.android.com/tools" android:installLocation="auto"> - <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" /> - <uses-permission android:name="android.permission.NFC" /> + <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"/> + <uses-permission android:name="android.permission.NFC"/> + <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> + + <uses-permission + android:name="android.permission.READ_PHONE_STATE" + tools:node="remove"/> <application android:allowBackup="true" @@ -22,136 +27,154 @@ android:label="@string/app_name" android:theme="@style/ConversationsTheme" tools:replace="android:label" - android:name=".ConversationsPlusApplication"> - <service android:name="de.thedevstack.conversationsplus.services.XmppConnectionService" /> + android:name="de.thedevstack.conversationsplus.ConversationsPlusApplication"> + <service android:name=".services.XmppConnectionService" /> - <receiver android:name="de.thedevstack.conversationsplus.services.EventReceiver" > + <receiver android:name=".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" /> + <action android:name="android.intent.action.BOOT_COMPLETED"/> + <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> + <action android:name="android.intent.action.ACTION_SHUTDOWN"/> + <action android:name="android.media.RINGER_MODE_CHANGED"/> </intent-filter> </receiver> <activity - android:name="de.thedevstack.conversationsplus.ui.ConversationActivity" + android:name=".ui.ConversationActivity" android:label="@string/app_name" android:launchMode="singleTask" - android:windowSoftInputMode="stateHidden" > + android:windowSoftInputMode="stateHidden"> <intent-filter> - <action android:name="android.intent.action.MAIN" /> + <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER" /> + <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity - android:name="de.thedevstack.conversationsplus.ui.StartConversationActivity" + android:name=".ui.StartConversationActivity" android:configChanges="orientation|screenSize" - android:label="@string/title_activity_start_conversation" > + android:label="@string/title_activity_start_conversation" + android:launchMode="singleTask"> <intent-filter> - <action android:name="android.intent.action.SENDTO" /> + <action android:name="android.intent.action.SENDTO"/> - <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.DEFAULT"/> - <data android:scheme="imto" /> - <data android:host="jabber" /> + <data android:scheme="imto"/> + <data android:host="jabber"/> </intent-filter> <intent-filter> - <action android:name="android.intent.action.VIEW" /> + <action android:name="android.intent.action.VIEW"/> - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> + <category android:name="android.intent.category.DEFAULT"/> + <category android:name="android.intent.category.BROWSABLE"/> - <data android:scheme="xmpp" /> + <data android:scheme="xmpp"/> </intent-filter> <intent-filter> - <action android:name="android.nfc.action.NDEF_DISCOVERED" /> + <action android:name="android.nfc.action.NDEF_DISCOVERED"/> - <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.DEFAULT"/> - <data android:scheme="xmpp" /> + <data android:scheme="xmpp"/> </intent-filter> </activity> <activity - android:name="de.thedevstack.conversationsplus.ui.SettingsActivity" - android:label="@string/title_activity_settings" /> + android:name=".ui.SettingsActivity" + android:label="@string/title_activity_settings"/> <activity - android:name="de.thedevstack.conversationsplus.ui.ChooseContactActivity" - android:label="@string/title_activity_choose_contact" /> + android:name=".ui.ChooseContactActivity" + android:label="@string/title_activity_choose_contact"/> <activity - android:name="de.thedevstack.conversationsplus.ui.BlocklistActivity" - android:label="@string/title_activity_block_list" /> - <activity - android:name="de.thedevstack.conversationsplus.ui.ChangePasswordActivity" - android:label="@string/change_password_on_server" /> + android:name=".ui.BlocklistActivity" + android:label="@string/title_activity_block_list"/> <activity - android:name="de.thedevstack.conversationsplus.ui.ManageAccountActivity" - android:configChanges="orientation|screenSize" - android:label="@string/title_activity_manage_accounts" /> + android:name=".ui.ChangePasswordActivity" + android:label="@string/change_password_on_server"/> + <activity + android:name=".ui.ManageAccountActivity" + android:label="@string/title_activity_manage_accounts" + android:launchMode="singleTask"/> <activity - android:name="de.thedevstack.conversationsplus.ui.EditAccountActivity" - android:windowSoftInputMode="stateHidden|adjustResize" /> + android:name=".ui.EditAccountActivity" + android:launchMode="singleTask" + android:windowSoftInputMode="stateHidden|adjustResize"/> <activity - android:name="de.thedevstack.conversationsplus.ui.ConferenceDetailsActivity" + android:name=".ui.ConferenceDetailsActivity" android:label="@string/title_activity_conference_details" - android:windowSoftInputMode="stateHidden" /> + android:windowSoftInputMode="stateHidden"/> <activity - android:name="de.thedevstack.conversationsplus.ui.ContactDetailsActivity" + android:name=".ui.ContactDetailsActivity" android:label="@string/title_activity_contact_details" - android:windowSoftInputMode="stateHidden" /> + android:windowSoftInputMode="stateHidden"/> <activity - android:name="de.thedevstack.conversationsplus.ui.PublishProfilePictureActivity" + android:name=".ui.PublishProfilePictureActivity" android:label="@string/mgmt_account_publish_avatar" - android:windowSoftInputMode="stateHidden" /> + android:windowSoftInputMode="stateHidden"/> <activity - android:name="de.thedevstack.conversationsplus.ui.VerifyOTRActivity" + android:name=".ui.VerifyOTRActivity" android:label="@string/verify_otr" - android:windowSoftInputMode="stateHidden" /> + android:windowSoftInputMode="stateHidden"/> <activity - android:name="de.thedevstack.conversationsplus.ui.ShareWithActivity" - android:label="@string/app_name" > + android:name=".ui.ShareWithActivity" + android:label="@string/app_name"> <intent-filter> - <action android:name="android.intent.action.SEND" /> + <action android:name="android.intent.action.SEND"/> - <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.DEFAULT"/> - <data android:mimeType="text/plain" /> + <data android:mimeType="text/plain"/> </intent-filter> <intent-filter> - <action android:name="android.intent.action.SEND" /> + <action android:name="android.intent.action.SEND"/> - <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.DEFAULT"/> - <data android:mimeType="*/*" /> + <data android:mimeType="*/*"/> </intent-filter> <intent-filter> - <action android:name="android.intent.action.SEND_MULTIPLE" /> + <action android:name="android.intent.action.SEND_MULTIPLE"/> - <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.DEFAULT"/> - <data android:mimeType="image/*" /> + <data android:mimeType="image/*"/> </intent-filter> + <meta-data + android:name="android.service.chooser.chooser_target_service" + android:value=".services.ContactChooserTargetService" /> </activity> <activity + android:name=".ui.TrustKeysActivity" + android:label="@string/trust_omemo_fingerprints" + android:windowSoftInputMode="stateAlwaysHidden"/> + <activity android:name="de.duenndns.ssl.MemorizingActivity" android:theme="@style/ConversationsTheme" tools:replace="android:theme"/> <activity - android:name="de.thedevstack.conversationsplus.ui.AboutActivity" + android:name=".ui.AboutActivity" android:label="@string/title_activity_about" - android:parentActivityName="de.thedevstack.conversationsplus.ui.SettingsActivity" > + android:parentActivityName=".ui.SettingsActivity"> <meta-data android:name="android.support.PARENT_ACTIVITY" - android:value="de.thedevstack.conversationsplus.ui.SettingsActivity" /> + android:value=".ui.SettingsActivity"/> </activity> <activity android:name="de.thedevstack.conversationsplus.ui.LogCatOutputActivity" android:label="@string/title_activity_loginformation" - android:parentActivityName="de.thedevstack.conversationsplus.ui.SettingsActivity" > + android:parentActivityName=".ui.SettingsActivity" > <meta-data android:name="android.support.PARENT_ACTIVITY" - android:value="de.thedevstack.conversationsplus.ui.SettingsActivity" /> + android:value=".ui.SettingsActivity" /> </activity> + <activity android:name="com.soundcloud.android.crop.CropImageActivity" /> + <service android:name=".services.ExportLogsService"/> + <service android:name=".services.ContactChooserTargetService" + android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE"> + <intent-filter> + <action android:name="android.service.chooser.ChooserTargetService" /> + </intent-filter> + </service> </application> </manifest> diff --git a/src/main/java/de/thedevstack/conversationsplus/Config.java b/src/main/java/de/thedevstack/conversationsplus/Config.java index 08e7d862..71227be0 100644 --- a/src/main/java/de/thedevstack/conversationsplus/Config.java +++ b/src/main/java/de/thedevstack/conversationsplus/Config.java @@ -6,19 +6,75 @@ import de.thedevstack.conversationsplus.xmpp.chatstate.ChatState; public final class Config { + + private static final int UNENCRYPTED = 1; + private static final int OPENPGP = 2; + private static final int OTR = 4; + private static final int OMEMO = 8; + + private static final int ENCRYPTION_MASK = UNENCRYPTED | OPENPGP | OTR | OMEMO; + + public static boolean supportUnencrypted() { + return (ENCRYPTION_MASK & UNENCRYPTED) != 0; + } + + public static boolean supportOpenPgp() { + return (ENCRYPTION_MASK & OPENPGP) != 0; + } + + public static boolean supportOpenPgpOnly() { + return supportOpenPgp() && !multipleEncryptionChoices(); + } + + public static boolean supportOtr() { + return (ENCRYPTION_MASK & OTR) != 0; + } + + public static boolean supportOmemo() { + return ConversationsPlusPreferences.omemoEnabled(); + } + + public static boolean multipleEncryptionChoices() { + return (ENCRYPTION_MASK & (ENCRYPTION_MASK - 1)) != 0; + } + public static final String LOGTAG = "conversations"; + + public static final String DOMAIN_LOCK = null; //only allow account creation for this domain + public static final String CONFERENCE_DOMAIN_LOCK = null; //only allow conference creation for this domain + public static final boolean LOCK_DOMAINS_IN_CONVERSATIONS = false; //only add contacts and conferences for own domains + + public static final boolean SINGLE_ACCOUNT = false; //set to true to allow only one account + public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox + + public static final boolean ALLOW_NON_TLS_CONNECTIONS = false; //very dangerous. you should have a good reason to set this to true + public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = false; + public static final boolean SHOW_CONNECTED_ACCOUNTS = false; //show number of connected accounts in foreground notification + + public static final boolean ALWAYS_NOTIFY_BY_DEFAULT = false; + + public static final boolean LEGACY_NAMESPACE_HTTP_UPLOAD = false; + 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 PING_TIMEOUT = 15; public static final int SOCKET_TIMEOUT = 15; public static final int CONNECT_TIMEOUT = 90; - public static final int CARBON_GRACE_PERIOD = 60; + public static final int CONNECT_DISCO_TIMEOUT = 20; + public static final int CARBON_GRACE_PERIOD = 90; public static final int MINI_GRACE_PERIOD = 750; + public static final boolean CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND = false; + public static final int AVATAR_SIZE = 192; public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.PNG; + public static final int IMAGE_SIZE = 1920; + public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG; + public static final int IMAGE_QUALITY = 75; + public static final int IMAGE_MAX_SIZE = 524288; //512KiB + public static final int MESSAGE_MERGE_WINDOW = 20; public static final boolean UTF8_EMOTICONS = false; @@ -29,13 +85,24 @@ public final class Config { public static final int PROGRESS_UI_UPDATE_INTERVAL = 750; public static final int REFRESH_UI_INTERVAL = 500; - public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb + public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb + public static final boolean DISABLE_HTTP_UPLOAD = false; public static final boolean DISABLE_STRING_PREP = false; // setting to true might increase startup performance - public static final boolean EXTENDED_SM_LOGGING = true; // log stanza counts + public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts public static final boolean RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE = true; //setting to true might increase power consumption public static final boolean ENCRYPT_ON_HTTP_UPLOADED = false; + public static final boolean REPORT_WRONG_FILESIZE_IN_OTR_JINGLE = true; + + public static final boolean SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON = false; + + public static final boolean X509_VERIFICATION = false; //use x509 certificates to verify OMEMO keys + + public static final boolean IGNORE_ID_REWRITE_IN_MUC = true; + + public static final boolean REQUEST_DISCO = true; + public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2; public static final int MAM_MAX_MESSAGES = 500; @@ -81,6 +148,5 @@ public final class Config { }; private Config() { - } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java index e4fabda5..f5f4c057 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java +++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java @@ -8,6 +8,7 @@ import android.preference.PreferenceManager; import java.io.File; import de.thedevstack.conversationsplus.utils.ImageUtil; +import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.utils.SerialSingleThreadExecutor; /** diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java index 17829998..20eca48a 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java +++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java @@ -14,6 +14,10 @@ public class ConversationsPlusPreferences extends Settings { private static ConversationsPlusPreferences instance; private final SharedPreferences sharedPreferences; + public static boolean omemoEnabled() { + return getBoolean("omemo_enabled", false); + } + public static String imgTransferFolder() { return getString("img_transfer_folder", getString("app_name", "Conversations+")); } @@ -168,9 +172,6 @@ public class ConversationsPlusPreferences extends Settings { public static boolean forceEncryption() { return getBoolean("force_encryption", false); } - public static boolean confirmMessages() { - return getBoolean("confirm_messages", true); - } public static boolean dontSaveEncrypted() { return getBoolean("dont_save_encrypted", false); @@ -193,6 +194,10 @@ public class ConversationsPlusPreferences extends Settings { return getBoolean("indicate_received", false); } + public static boolean allowMessageCorrection() { + return getBoolean("allow_message_correction", true); + } + private ConversationsPlusPreferences(SharedPreferences sharedPreferences) { this.sharedPreferences = sharedPreferences; } diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/OtrService.java b/src/main/java/de/thedevstack/conversationsplus/crypto/OtrService.java index e36007d7..34023d9f 100644 --- a/src/main/java/de/thedevstack/conversationsplus/crypto/OtrService.java +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/OtrService.java @@ -1,5 +1,18 @@ package de.thedevstack.conversationsplus.crypto; +import net.java.otr4j.OtrEngineHost; +import net.java.otr4j.OtrException; +import net.java.otr4j.OtrPolicy; +import net.java.otr4j.OtrPolicyImpl; +import net.java.otr4j.crypto.OtrCryptoEngineImpl; +import net.java.otr4j.crypto.OtrCryptoException; +import net.java.otr4j.session.FragmenterInstructions; +import net.java.otr4j.session.InstanceTag; +import net.java.otr4j.session.SessionID; + +import org.json.JSONException; +import org.json.JSONObject; + import java.math.BigInteger; import java.security.KeyFactory; import java.security.KeyPair; @@ -11,32 +24,18 @@ import java.security.spec.DSAPrivateKeySpec; import java.security.spec.DSAPublicKeySpec; import java.security.spec.InvalidKeySpecException; -import org.json.JSONException; -import org.json.JSONObject; - import de.thedevstack.android.logcat.Logging; - -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.generator.MessageGenerator; import de.thedevstack.conversationsplus.services.XmppConnectionService; -import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.xmpp.chatstate.ChatState; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.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.crypto.OtrCryptoEngineImpl; -import net.java.otr4j.crypto.OtrCryptoException; -import net.java.otr4j.session.InstanceTag; -import net.java.otr4j.session.SessionID; -import net.java.otr4j.session.FragmenterInstructions; - public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost { private Account account; @@ -181,10 +180,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost { packet.setAttribute("to", session.getAccountID() + "/" + session.getUserID()); } packet.setBody(body); - packet.addChild("private", "urn:xmpp:carbons:2"); - packet.addChild("no-copy", "urn:xmpp:hints"); - packet.addChild("no-permanent-store", "urn:xmpp:hints"); - packet.addChild("no-permanent-storage", "urn:xmpp:hints"); + MessageGenerator.addMessageHints(packet); try { Jid jid = Jid.fromSessionID(session); Conversation conversation = mXmppConnectionService.find(account,jid); diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/PgpDecryptionService.java b/src/main/java/de/thedevstack/conversationsplus/crypto/PgpDecryptionService.java new file mode 100644 index 00000000..b833f6e4 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/PgpDecryptionService.java @@ -0,0 +1,162 @@ +package de.thedevstack.conversationsplus.crypto; + +import android.app.PendingIntent; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.services.XmppConnectionService; +import de.thedevstack.conversationsplus.ui.UiCallback; + +public class PgpDecryptionService { + + private final XmppConnectionService xmppConnectionService; + private final ConcurrentHashMap<String, List<Message>> messages = new ConcurrentHashMap<>(); + private final ConcurrentHashMap<String, Boolean> decryptingMessages = new ConcurrentHashMap<>(); + private Boolean keychainLocked = false; + private final Object keychainLockedLock = new Object(); + + public PgpDecryptionService(XmppConnectionService xmppConnectionService) { + this.xmppConnectionService = xmppConnectionService; + } + + public void add(Message message) { + if (isRunning()) { + decryptDirectly(message); + } else { + store(message); + } + } + + public void addAll(List<Message> messagesList) { + if (!messagesList.isEmpty()) { + String conversationUuid = messagesList.get(0).getConversation().getUuid(); + if (!messages.containsKey(conversationUuid)) { + List<Message> list = Collections.synchronizedList(new LinkedList<Message>()); + messages.put(conversationUuid, list); + } + synchronized (messages.get(conversationUuid)) { + messages.get(conversationUuid).addAll(messagesList); + } + decryptAllMessages(); + } + } + + public void onKeychainUnlocked() { + synchronized (keychainLockedLock) { + keychainLocked = false; + } + decryptAllMessages(); + } + + public void onKeychainLocked() { + synchronized (keychainLockedLock) { + keychainLocked = true; + } + xmppConnectionService.updateConversationUi(); + } + + public void onOpenPgpServiceBound() { + decryptAllMessages(); + } + + public boolean isRunning() { + synchronized (keychainLockedLock) { + return !keychainLocked; + } + } + + private void store(Message message) { + if (messages.containsKey(message.getConversation().getUuid())) { + messages.get(message.getConversation().getUuid()).add(message); + } else { + List<Message> messageList = Collections.synchronizedList(new LinkedList<Message>()); + messageList.add(message); + messages.put(message.getConversation().getUuid(), messageList); + } + } + + private void decryptAllMessages() { + for (String uuid : messages.keySet()) { + decryptMessages(uuid); + } + } + + private void decryptMessages(final String uuid) { + synchronized (decryptingMessages) { + Boolean decrypting = decryptingMessages.get(uuid); + if ((decrypting != null && !decrypting) || decrypting == null) { + decryptingMessages.put(uuid, true); + decryptMessage(uuid); + } + } + } + + private void decryptMessage(final String uuid) { + Message message = null; + synchronized (messages.get(uuid)) { + while (!messages.get(uuid).isEmpty()) { + if (messages.get(uuid).get(0).getEncryption() == Message.ENCRYPTION_PGP) { + if (isRunning()) { + message = messages.get(uuid).remove(0); + } + break; + } else { + messages.get(uuid).remove(0); + } + } + if (message != null && xmppConnectionService.getPgpEngine() != null) { + xmppConnectionService.getPgpEngine().decrypt(message, new UiCallback<Message>() { + + @Override + public void userInputRequried(PendingIntent pi, Message message) { + messages.get(uuid).add(0, message); + decryptingMessages.put(uuid, false); + } + + @Override + public void success(Message message) { + xmppConnectionService.updateConversationUi(); + decryptMessage(uuid); + } + + @Override + public void error(int error, Message message) { + message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); + xmppConnectionService.updateConversationUi(); + decryptMessage(uuid); + } + }); + } else { + decryptingMessages.put(uuid, false); + } + } + } + + private void decryptDirectly(final Message message) { + if (message.getEncryption() == Message.ENCRYPTION_PGP && xmppConnectionService.getPgpEngine() != null) { + xmppConnectionService.getPgpEngine().decrypt(message, new UiCallback<Message>() { + + @Override + public void userInputRequried(PendingIntent pi, Message message) { + store(message); + } + + @Override + public void success(Message message) { + xmppConnectionService.updateConversationUi(); + xmppConnectionService.getNotificationService().updateNotification(false); + } + + @Override + public void error(int error, Message message) { + message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); + xmppConnectionService.updateConversationUi(); + } + }); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java b/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java index 08aa8272..8a5c6101 100644 --- a/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/PgpEngine.java @@ -1,5 +1,13 @@ package de.thedevstack.conversationsplus.crypto; +import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; + +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.openintents.openpgp.util.OpenPgpApi; +import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; @@ -9,12 +17,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URL; -import org.openintents.openpgp.OpenPgpSignatureResult; -import org.openintents.openpgp.util.OpenPgpApi; -import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; - import de.thedevstack.conversationsplus.ConversationsPlusPreferences; -import de.thedevstack.conversationsplus.persistance.FileBackend; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.utils.StreamUtil; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Contact; @@ -22,13 +27,9 @@ import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.http.HttpConnectionManager; +import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.ui.UiCallback; -import de.thedevstack.conversationsplus.utils.MessageUtil; - -import android.app.PendingIntent; -import android.content.Intent; -import android.net.Uri; public class PgpEngine { private OpenPgpApi api; @@ -39,26 +40,24 @@ public class PgpEngine { this.mXmppConnectionService = service; } - public void decrypt(final Message message, - final UiCallback<Message> callback) { + public void decrypt(final Message message, final UiCallback<Message> callback) { Intent params = new Intent(); params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); - params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message - .getConversation().getAccount().getJid().toBareJid().toString()); + final String uuid = message.getUuid(); if (message.getType() == Message.TYPE_TEXT) { - InputStream is = new ByteArrayInputStream(message.getBody() - .getBytes()); + 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)) { + notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_DECRYPT_VERIFY, 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) { + if (message.getEncryption() == Message.ENCRYPTION_PGP + && message.getUuid().equals(uuid)) { message.setBody(os.toString()); message.setEncryption(Message.ENCRYPTION_DECRYPTED); final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager(); @@ -69,6 +68,7 @@ public class PgpEngine { && ConversationsPlusPreferences.autoAcceptFileSize() > 0) { manager.createNewDownloadConnection(message); } + mXmppConnectionService.updateMessage(message); callback.success(message); } } catch (IOException e) { @@ -99,6 +99,7 @@ public class PgpEngine { @Override public void onReturn(Intent result) { + notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_DECRYPT_VERIFY, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: @@ -131,21 +132,19 @@ public class PgpEngine { } } - public void encrypt(final Message message, - final UiCallback<Message> callback) { - + 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() }; + final Conversation conversation = message.getConversation(); + if (conversation.getMode() == Conversation.MODE_SINGLE) { + long[] keys = { + conversation.getContact().getPgpKeyId(), + conversation.getAccount().getPgpId() + }; params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys); } else { - params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, message.getConversation() - .getMucOptions().getPgpKeyIds()); + params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds()); } - params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message - .getConversation().getAccount().getJid().toBareJid().toString()); if (!message.needsUploading()) { params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); @@ -161,6 +160,7 @@ public class PgpEngine { @Override public void onReturn(Intent result) { + notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: @@ -198,15 +198,22 @@ public class PgpEngine { DownloadableFile outputFile = FileBackend.getFile(message, false); outputFile.getParentFile().mkdirs(); outputFile.createNewFile(); - InputStream is = new FileInputStream(inputFile); - OutputStream os = new FileOutputStream(outputFile); + final InputStream is = new FileInputStream(inputFile); + final OutputStream os = new FileOutputStream(outputFile); api.executeApiAsync(params, is, os, new IOpenPgpCallback() { @Override public void onReturn(Intent result) { + notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: + try { + os.flush(); + } catch (IOException ignored) { + //ignored + } + StreamUtil.close(os); callback.success(message); break; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: @@ -249,10 +256,10 @@ public class PgpEngine { 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().toBareJid().toString()); InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes()); ByteArrayOutputStream os = new ByteArrayOutputStream(); Intent result = api.executeApi(params, is, os); + notifyPgpDecryptionService(account, OpenPgpApi.ACTION_DECRYPT_VERIFY, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { case OpenPgpApi.RESULT_CODE_SUCCESS: @@ -271,18 +278,45 @@ public class PgpEngine { return 0; } + public void chooseKey(final Account account, final UiCallback<Account> callback) { + Intent p = new Intent(); + p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID); + api.executeApiAsync(p, null, null, new IOpenPgpCallback() { + + @Override + public void onReturn(Intent result) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + 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); + } + } + }); + } + public void generateSignature(final Account account, String status, final UiCallback<Account> callback) { + if (account.getPgpId() == -1) { + return; + } Intent params = new Intent(); + params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN); params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - params.setAction(OpenPgpApi.ACTION_SIGN); - params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString()); + params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId()); InputStream is = new ByteArrayInputStream(status.getBytes()); final OutputStream os = new ByteArrayOutputStream(); api.executeApiAsync(params, is, os, new IOpenPgpCallback() { @Override public void onReturn(Intent result) { + notifyPgpDecryptionService(account, OpenPgpApi.ACTION_SIGN, result); switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { case OpenPgpApi.RESULT_CODE_SUCCESS: StringBuilder signatureBuilder = new StringBuilder(); @@ -308,7 +342,7 @@ public class PgpEngine { callback.error(R.string.openpgp_error, account); return; } - account.setKey("pgp_signature", signatureBuilder.toString()); + account.setPgpSignature(signatureBuilder.toString()); callback.success(account); return; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: @@ -327,8 +361,6 @@ public class PgpEngine { 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().toBareJid().toString()); api.executeApiAsync(params, null, null, new IOpenPgpCallback() { @Override @@ -353,8 +385,6 @@ public class PgpEngine { 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().toBareJid().toString()); Intent result = api.executeApi(params, null, null); return (PendingIntent) result .getParcelableExtra(OpenPgpApi.RESULT_INTENT); @@ -364,9 +394,21 @@ public class PgpEngine { 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().toBareJid().toString()); Intent result = api.executeApi(params, null, null); return (PendingIntent) result .getParcelableExtra(OpenPgpApi.RESULT_INTENT); } + + private void notifyPgpDecryptionService(Account account, String action, final Intent result) { + switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { + case OpenPgpApi.RESULT_CODE_SUCCESS: + if (OpenPgpApi.ACTION_SIGN.equals(action)) { + account.getPgpDecryptionService().onKeychainUnlocked(); + } + break; + case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: + account.getPgpDecryptionService().onKeychainLocked(); + break; + } + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/XmppDomainVerifier.java b/src/main/java/de/thedevstack/conversationsplus/crypto/XmppDomainVerifier.java new file mode 100644 index 00000000..c9f65a97 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/XmppDomainVerifier.java @@ -0,0 +1,127 @@ +package de.thedevstack.conversationsplus.crypto; + +import android.util.Log; +import android.util.Pair; + +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.DERIA5String; +import org.bouncycastle.asn1.DERTaggedObject; +import org.bouncycastle.asn1.DERUTF8String; +import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; + +import java.io.IOException; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +public class XmppDomainVerifier implements HostnameVerifier { + + private static final String LOGTAG = "XmppDomainVerifier"; + + private final String SRVName = "1.3.6.1.5.5.7.8.7"; + private final String xmppAddr = "1.3.6.1.5.5.7.8.5"; + + @Override + public boolean verify(String domain, SSLSession sslSession) { + try { + Certificate[] chain = sslSession.getPeerCertificates(); + if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) { + return false; + } + X509Certificate certificate = (X509Certificate) chain[0]; + Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames(); + List<String> xmppAddrs = new ArrayList<>(); + List<String> srvNames = new ArrayList<>(); + List<String> domains = new ArrayList<>(); + if (alternativeNames != null) { + for (List<?> san : alternativeNames) { + Integer type = (Integer) san.get(0); + if (type == 0) { + Pair<String, String> otherName = parseOtherName((byte[]) san.get(1)); + if (otherName != null) { + switch (otherName.first) { + case SRVName: + srvNames.add(otherName.second); + break; + case xmppAddr: + xmppAddrs.add(otherName.second); + break; + default: + Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second); + } + } + } else if (type == 2) { + Object value = san.get(1); + if (value instanceof String) { + domains.add((String) value); + } + } + } + } + if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) { + X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); + RDN[] rdns = x500name.getRDNs(BCStyle.CN); + for (int i = 0; i < rdns.length; ++i) { + domains.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue())); + } + } + Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + srvNames + " xmppAddrs: " + xmppAddrs + " domains:" + domains); + return xmppAddrs.contains(domain) || srvNames.contains("_xmpp-client." + domain) || matchDomain(domain, domains); + } catch (Exception e) { + return false; + } + } + + private static Pair<String, String> parseOtherName(byte[] otherName) { + try { + ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(otherName); + if (asn1Primitive instanceof DERTaggedObject) { + ASN1Primitive inner = ((DERTaggedObject) asn1Primitive).getObject(); + if (inner instanceof DLSequence) { + DLSequence sequence = (DLSequence) inner; + if (sequence.size() >= 2 && sequence.getObjectAt(1) instanceof DERTaggedObject) { + String oid = sequence.getObjectAt(0).toString(); + ASN1Primitive value = ((DERTaggedObject) sequence.getObjectAt(1)).getObject(); + if (value instanceof DERUTF8String) { + return new Pair<>(oid, ((DERUTF8String) value).getString()); + } else if (value instanceof DERIA5String) { + return new Pair<>(oid, ((DERIA5String) value).getString()); + } + } + } + } + return null; + } catch (IOException e) { + return null; + } + } + + private static boolean matchDomain(String needle, List<String> haystack) { + for (String entry : haystack) { + if (entry.startsWith("*.")) { + int i = needle.indexOf('.'); + Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1)); + if (i != -1 && needle.substring(i).equals(entry.substring(1))) { + Log.d(LOGTAG, "domain " + needle + " matched " + entry); + return true; + } + } else { + if (entry.equals(needle)) { + Log.d(LOGTAG, "domain " + needle + " matched " + entry); + return true; + } + } + } + return false; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlService.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlService.java new file mode 100644 index 00000000..987cf940 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlService.java @@ -0,0 +1,110 @@ +package de.thedevstack.conversationsplus.crypto.axolotl; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; + +import java.security.cert.X509Certificate; +import java.util.Set; + +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Contact; +import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.xmpp.OnAdvancedStreamFeaturesLoaded; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; + +/** + * Created by tzur on 02.03.2016. + */ +public interface AxolotlService extends OnAdvancedStreamFeaturesLoaded { + + String LOGPREFIX = "AxolotlService"; + + String PEP_PREFIX = "eu.siacs.conversations.axolotl"; + String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist"; + String PEP_BUNDLES = PEP_PREFIX + ".bundles"; + String PEP_VERIFICATION = PEP_PREFIX + ".verification"; + + enum FetchStatus { + PENDING, + SUCCESS, + SUCCESS_VERIFIED, + TIMEOUT, + ERROR + } + + boolean fetchMapHasErrors(Contact contact); + + String getOwnFingerprint(); + + Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust); + + Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact); + + long getNumTrustedKeys(Contact contact); + + Set<String> getFingerprintsForOwnSessions(); + + Set<String> getFingerprintsForContact(Contact contact); + + boolean isPepBroken(); + + void regenerateKeys(boolean wipeOther); + + int getOwnDeviceId(); + + Set<Integer> getOwnDeviceIds(); + + void registerDevices(Jid jid, @NonNull Set<Integer> deviceIds); + + void wipeOtherPepDevices(); + + void purgeKey(String fingerprint); + + void publishOwnDeviceIdIfNeeded(); + + void publishOwnDeviceId(Set<Integer> deviceIds); + + void publishDeviceVerificationAndBundle(SignedPreKeyRecord signedPreKeyRecord, + Set<PreKeyRecord> preKeyRecords, + boolean announceAfter, + boolean wipe); + + void publishBundlesIfNeeded(boolean announce, boolean wipe); + + boolean isContactAxolotlCapable(Contact contact); + + XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint); + + X509Certificate getFingerprintCertificate(String fingerprint); + + void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust); + + Set<AxolotlAddress> findDevicesWithoutSession(Conversation conversation); + + Set<AxolotlAddress> findDevicesWithoutSession(Jid contactJid); + + boolean createSessionsIfNeeded(Conversation conversation); + + boolean trustedSessionVerified(Conversation conversation); + + boolean hasPendingKeyFetches(Account account, Contact contact); + + @Nullable + XmppAxolotlMessage encrypt(Message message); + + void preparePayloadMessage(Message message, boolean delay); + + void prepareKeyTransportMessage(Contact contact, OnMessageCreatedCallback onMessageCreatedCallback); + + XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message); + + XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message); + + XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message); +} diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java new file mode 100644 index 00000000..5a05b34c --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java @@ -0,0 +1,1021 @@ +package de.thedevstack.conversationsplus.crypto.axolotl; + +import android.os.Bundle; +import android.security.KeyChain; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; +import android.util.Pair; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.IdentityKeyPair; +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.SessionBuilder; +import org.whispersystems.libaxolotl.UntrustedIdentityException; +import org.whispersystems.libaxolotl.ecc.ECPublicKey; +import org.whispersystems.libaxolotl.state.PreKeyBundle; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; +import org.whispersystems.libaxolotl.util.KeyHelper; + +import java.security.PrivateKey; +import java.security.Security; +import java.security.Signature; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Contact; +import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.parser.IqParser; +import de.thedevstack.conversationsplus.services.XmppConnectionService; +import de.thedevstack.conversationsplus.utils.CryptoHelper; +import de.thedevstack.conversationsplus.utils.SerialSingleThreadExecutor; +import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; +import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +public class AxolotlServiceImpl implements AxolotlService { + + public static final int NUM_KEYS_TO_PUBLISH = 100; + public static final int publishTriesThreshold = 3; + + private final Account account; + private final XmppConnectionService mXmppConnectionService; + private final SQLiteAxolotlStore axolotlStore; + private final SessionMap sessions; + private final Map<Jid, Set<Integer>> deviceIds; + private final Map<String, XmppAxolotlMessage> messageCache; + private final FetchStatusMap fetchStatusMap; + private final SerialSingleThreadExecutor executor; + private int numPublishTriesOnEmptyPep = 0; + private boolean pepBroken = false; + + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().pep()) { + publishBundlesIfNeeded(true, false); + } else { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping OMEMO initialization"); + } + } + + @Override + public boolean fetchMapHasErrors(Contact contact) { + Jid jid = contact.getJid().toBareJid(); + if (deviceIds.get(jid) != null) { + for (Integer foreignId : this.deviceIds.get(jid)) { + AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId); + if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) { + return true; + } + } + } + return false; + } + + private static class AxolotlAddressMap<T> { + protected Map<String, Map<Integer, T>> map; + protected final Object MAP_LOCK = new Object(); + + public AxolotlAddressMap() { + this.map = new HashMap<>(); + } + + public void put(AxolotlAddress address, T value) { + synchronized (MAP_LOCK) { + Map<Integer, T> devices = map.get(address.getName()); + if (devices == null) { + devices = new HashMap<>(); + map.put(address.getName(), devices); + } + devices.put(address.getDeviceId(), value); + } + } + + public T get(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map<Integer, T> devices = map.get(address.getName()); + if (devices == null) { + return null; + } + return devices.get(address.getDeviceId()); + } + } + + public Map<Integer, T> getAll(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map<Integer, T> devices = map.get(address.getName()); + if (devices == null) { + return new HashMap<>(); + } + return devices; + } + } + + public boolean hasAny(AxolotlAddress address) { + synchronized (MAP_LOCK) { + Map<Integer, T> devices = map.get(address.getName()); + return devices != null && !devices.isEmpty(); + } + } + + public void clear() { + map.clear(); + } + + } + + private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> { + private final XmppConnectionService xmppConnectionService; + private final Account account; + + public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) { + super(); + this.xmppConnectionService = service; + this.account = account; + this.fillMap(store); + } + + private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) { + for (Integer deviceId : deviceIds) { + AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId); + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Building session for remote address: " + axolotlAddress.toString()); + IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey(); + if(Config.X509_VERIFICATION) { + X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", "")); + if (certificate != null) { + Bundle information = CryptoHelper.extractCertificateInformation(certificate); + try { + final String cn = information.getString("subject_cn"); + final Jid jid = Jid.fromString(bareJid); + Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn); + account.getRoster().getContact(jid).setCommonName(cn); + } catch (final InvalidJidException ignored) { + //ignored + } + } + } + this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey)); + } + } + + private void fillMap(SQLiteAxolotlStore store) { + List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toString()); + putDevicesForJid(account.getJid().toBareJid().toString(), deviceIds, store); + for (Contact contact : account.getRoster().getContacts()) { + Jid bareJid = contact.getJid().toBareJid(); + String address = bareJid.toString(); + deviceIds = store.getSubDeviceSessions(address); + putDevicesForJid(address, deviceIds, store); + } + + } + + @Override + public void put(AxolotlAddress address, XmppAxolotlSession value) { + super.put(address, value); + value.setNotFresh(); + xmppConnectionService.syncRosterToDisk(account); + } + + public void put(XmppAxolotlSession session) { + this.put(session.getRemoteAddress(), session); + } + } + + private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> { + + } + + public static String getLogprefix(Account account) { + return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): "; + } + + public AxolotlServiceImpl(Account account, XmppConnectionService connectionService) { + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } + this.mXmppConnectionService = connectionService; + this.account = account; + this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService); + this.deviceIds = new HashMap<>(); + this.messageCache = new HashMap<>(); + this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account); + this.fetchStatusMap = new FetchStatusMap(); + this.executor = new SerialSingleThreadExecutor(); + } + + @Override + public String getOwnFingerprint() { + return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", ""); + } + + @Override + public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) { + return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust); + } + + @Override + public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact) { + return axolotlStore.getContactKeysWithTrust(contact.getJid().toBareJid().toString(), trust); + } + + @Override + public long getNumTrustedKeys(Contact contact) { + return axolotlStore.getContactNumTrustedKeys(contact.getJid().toBareJid().toString()); + } + + private AxolotlAddress getAddressForJid(Jid jid) { + return new AxolotlAddress(jid.toString(), 0); + } + + private Set<XmppAxolotlSession> findOwnSessions() { + AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid()); + return new HashSet<>(this.sessions.getAll(ownAddress).values()); + } + + private Set<XmppAxolotlSession> findSessionsforContact(Contact contact) { + AxolotlAddress contactAddress = getAddressForJid(contact.getJid()); + return new HashSet<>(this.sessions.getAll(contactAddress).values()); + } + + @Override + public Set<String> getFingerprintsForOwnSessions() { + Set<String> fingerprints = new HashSet<>(); + for (XmppAxolotlSession session : findOwnSessions()) { + fingerprints.add(session.getFingerprint()); + } + return fingerprints; + } + + @Override + public Set<String> getFingerprintsForContact(final Contact contact) { + Set<String> fingerprints = new HashSet<>(); + for (XmppAxolotlSession session : findSessionsforContact(contact)) { + fingerprints.add(session.getFingerprint()); + } + return fingerprints; + } + + private boolean hasAny(Contact contact) { + AxolotlAddress contactAddress = getAddressForJid(contact.getJid()); + return sessions.hasAny(contactAddress); + } + + @Override + public boolean isPepBroken() { + return this.pepBroken; + } + + @Override + public void regenerateKeys(boolean wipeOther) { + axolotlStore.regenerate(); + sessions.clear(); + fetchStatusMap.clear(); + publishBundlesIfNeeded(true, wipeOther); + } + + @Override + public int getOwnDeviceId() { + return axolotlStore.getLocalRegistrationId(); + } + + @Override + public Set<Integer> getOwnDeviceIds() { + return this.deviceIds.get(account.getJid().toBareJid()); + } + + private void setTrustOnSessions(final Jid jid, @NonNull final Set<Integer> deviceIds, + final XmppAxolotlSession.Trust from, + final XmppAxolotlSession.Trust to) { + for (Integer deviceId : deviceIds) { + AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId); + XmppAxolotlSession session = sessions.get(address); + if (session != null && session.getFingerprint() != null + && session.getTrust() == from) { + session.setTrust(to); + } + } + } + + @Override + public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) { + if (jid.toBareJid().equals(account.getJid().toBareJid())) { + if (!deviceIds.isEmpty()) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attemps and pepBroken status."); + pepBroken = false; + numPublishTriesOnEmptyPep = 0; + } + if (deviceIds.contains(getOwnDeviceId())) { + deviceIds.remove(getOwnDeviceId()); + } else { + publishOwnDeviceId(deviceIds); + } + for (Integer deviceId : deviceIds) { + AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId); + if (sessions.get(ownDeviceAddress) == null) { + buildSessionFromPEP(ownDeviceAddress); + } + } + } + Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString())); + expiredDevices.removeAll(deviceIds); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED, + XmppAxolotlSession.Trust.INACTIVE_TRUSTED); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED_X509, + XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNDECIDED, + XmppAxolotlSession.Trust.INACTIVE_UNDECIDED); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNTRUSTED, + XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED); + Set<Integer> newDevices = new HashSet<>(deviceIds); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED, + XmppAxolotlSession.Trust.TRUSTED); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509, + XmppAxolotlSession.Trust.TRUSTED_X509); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNDECIDED, + XmppAxolotlSession.Trust.UNDECIDED); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED, + XmppAxolotlSession.Trust.UNTRUSTED); + this.deviceIds.put(jid, deviceIds); + mXmppConnectionService.keyStatusUpdated(null); + } + + @Override + public void wipeOtherPepDevices() { + if (pepBroken) { + Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... "); + return; + } + Set<Integer> deviceIds = new HashSet<>(); + deviceIds.add(getOwnDeviceId()); + IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Wiping all other devices from Pep:" + publish); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + // TODO: implement this! + } + }); + } + + @Override + public void purgeKey(final String fingerprint) { + axolotlStore.setFingerprintTrust(fingerprint.replaceAll("\\s", ""), XmppAxolotlSession.Trust.COMPROMISED); + } + + @Override + public void publishOwnDeviceIdIfNeeded() { + if (pepBroken) { + Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... "); + return; + } + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids."); + } else { + Element item = mXmppConnectionService.getIqParser().getItem(packet); + Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + if (!deviceIds.contains(getOwnDeviceId())) { + publishOwnDeviceId(deviceIds); + } + } + } + }); + } + + @Override + public void publishOwnDeviceId(Set<Integer> deviceIds) { + Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds); + if (!deviceIdsCopy.contains(getOwnDeviceId())) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist."); + if (deviceIdsCopy.isEmpty()) { + if (numPublishTriesOnEmptyPep >= publishTriesThreshold) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting..."); + pepBroken = true; + return; + } else { + numPublishTriesOnEmptyPep++; + Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")"); + } + } else { + numPublishTriesOnEmptyPep = 0; + } + deviceIdsCopy.add(getOwnDeviceId()); + IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() != IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error")); + } + } + }); + } + } + + @Override + public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord, + final Set<PreKeyRecord> preKeyRecords, + final boolean announceAfter, + final boolean wipe) { + try { + IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey(); + PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias()); + X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias()); + Signature verifier = Signature.getInstance("sha256WithRSA"); + verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG()); + verifier.update(axolotlPublicKey.serialize()); + byte[] signature = verifier.sign(); + IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId()); + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + ": publish verification for device "+getOwnDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) { + if (pepBroken) { + Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... "); + return; + } + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + return; //ignore timeout. do nothing + } + + if (packet.getType() == IqPacket.TYPE.ERROR) { + Element error = packet.findChild("error"); + if (error == null || !error.hasChild("item-not-found")) { + pepBroken = true; + Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet); + return; + } + } + + PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet); + Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); + boolean flush = false; + if (bundle == null) { + Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Received invalid bundle:" + packet); + bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null); + flush = true; + } + if (keys == null) { + Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Received invalid prekeys:" + packet); + } + try { + boolean changed = false; + // Validate IdentityKey + IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair(); + if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) { + Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); + changed = true; + } + + // Validate signedPreKeyRecord + ID + SignedPreKeyRecord signedPreKeyRecord; + int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size(); + try { + signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId()); + if (flush + || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) + || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) { + Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); + axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); + changed = true; + } + } catch (InvalidKeyIdException e) { + Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); + axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); + changed = true; + } + + // Validate PreKeys + Set<PreKeyRecord> preKeyRecords = new HashSet<>(); + if (keys != null) { + for (Integer id : keys.keySet()) { + try { + PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id); + if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) { + preKeyRecords.add(preKeyRecord); + } + } catch (InvalidKeyIdException ignored) { + } + } + } + int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size(); + if (newKeys > 0) { + List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys( + axolotlStore.getCurrentPreKeyId() + 1, newKeys); + preKeyRecords.addAll(newRecords); + for (PreKeyRecord record : newRecords) { + axolotlStore.storePreKey(record.getId(), record); + } + changed = true; + Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP."); + } + + + if (changed) { + if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) { + mXmppConnectionService.publishDisplayName(account); + publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); + } else { + publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); + } + } else { + Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current"); + if (wipe) { + wipeOtherPepDevices(); + } else if (announce) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); + publishOwnDeviceIdIfNeeded(); + } + } + } catch (InvalidKeyException e) { + Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); + } + } + }); + } + + private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord, + Set<PreKeyRecord> preKeyRecords, + final boolean announceAfter, + final boolean wipe) { + IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( + signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), + preKeyRecords, getOwnDeviceId()); + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish); + mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Successfully published bundle. "); + if (wipe) { + wipeOtherPepDevices(); + } else if (announceAfter) { + Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); + publishOwnDeviceIdIfNeeded(); + } + } else { + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error")); + } + } + }); + } + + @Override + public boolean isContactAxolotlCapable(Contact contact) { + Jid jid = contact.getJid().toBareJid(); + return hasAny(contact) || + (deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty()); + } + + @Override + public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) { + return axolotlStore.getFingerprintTrust(fingerprint); + } + + @Override + public X509Certificate getFingerprintCertificate(String fingerprint) { + return axolotlStore.getFingerprintCertificate(fingerprint); + } + + @Override + public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) { + axolotlStore.setFingerprintTrust(fingerprint, trust); + } + + private void verifySessionWithPEP(final XmppAxolotlSession session) { + Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep"); + final AxolotlAddress address = session.getRemoteAddress(); + final IdentityKey identityKey = session.getIdentityKey(); + try { + IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet); + if (verification != null) { + try { + Signature verifier = Signature.getInstance("sha256WithRSA"); + verifier.initVerify(verification.first[0]); + verifier.update(identityKey.serialize()); + if (verifier.verify(verification.second)) { + try { + mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA"); + String fingerprint = session.getFingerprint(); + Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint); + setFingerprintTrust(fingerprint, XmppAxolotlSession.Trust.TRUSTED_X509); + axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]); + fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED); + Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]); + try { + final String cn = information.getString("subject_cn"); + final Jid jid = Jid.fromString(address.getName()); + Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn); + account.getRoster().getContact(jid).setCommonName(cn); + } catch (final InvalidJidException ignored) { + //ignored + } + finishBuildingSessionsFromPEP(address); + return; + } catch (Exception e) { + Log.d(Config.LOGTAG,"could not verify certificate"); + } + } + } catch (Exception e) { + Log.d(Config.LOGTAG, "error during verification " + e.getMessage()); + } + } else { + Log.d(Config.LOGTAG,"no verification found"); + } + fetchStatusMap.put(address, FetchStatus.SUCCESS); + finishBuildingSessionsFromPEP(address); + } + }); + } catch (InvalidJidException e) { + fetchStatusMap.put(address, FetchStatus.SUCCESS); + finishBuildingSessionsFromPEP(address); + } + } + + private void finishBuildingSessionsFromPEP(final AxolotlAddress address) { + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0); + if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) + && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) { + FetchStatus report = null; + if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.SUCCESS_VERIFIED) + | fetchStatusMap.getAll(address).containsValue(FetchStatus.SUCCESS_VERIFIED)) { + report = FetchStatus.SUCCESS_VERIFIED; + } else if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.ERROR) + || fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) { + report = FetchStatus.ERROR; + } + mXmppConnectionService.keyStatusUpdated(report); + } + } + + private void buildSessionFromPEP(final AxolotlAddress address) { + Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Building new sesstion for " + address.toString()); + if (address.getDeviceId() == getOwnDeviceId()) { + throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!"); + } + + try { + IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice( + Jid.fromString(address.getName()), address.getDeviceId()); + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket); + mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + fetchStatusMap.put(address, FetchStatus.TIMEOUT); + } else if (packet.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Received preKey IQ packet, processing..."); + final IqParser parser = mXmppConnectionService.getIqParser(); + final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet); + final PreKeyBundle bundle = parser.bundle(packet); + if (preKeyBundleList.isEmpty() || bundle == null) { + Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "preKey IQ packet invalid: " + packet); + fetchStatusMap.put(address, FetchStatus.ERROR); + finishBuildingSessionsFromPEP(address); + return; + } + Random random = new Random(); + final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size())); + if (preKey == null) { + //should never happen + fetchStatusMap.put(address, FetchStatus.ERROR); + finishBuildingSessionsFromPEP(address); + return; + } + + final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(), + preKey.getPreKeyId(), preKey.getPreKey(), + bundle.getSignedPreKeyId(), bundle.getSignedPreKey(), + bundle.getSignedPreKeySignature(), bundle.getIdentityKey()); + + try { + SessionBuilder builder = new SessionBuilder(axolotlStore, address); + builder.process(preKeyBundle); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey()); + sessions.put(address, session); + if (Config.X509_VERIFICATION) { + verifySessionWithPEP(session); + } else { + fetchStatusMap.put(address, FetchStatus.SUCCESS); + finishBuildingSessionsFromPEP(address); + } + } catch (UntrustedIdentityException | InvalidKeyException e) { + Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Error building session for " + address + ": " + + e.getClass().getName() + ", " + e.getMessage()); + fetchStatusMap.put(address, FetchStatus.ERROR); + finishBuildingSessionsFromPEP(address); + } + } else { + fetchStatusMap.put(address, FetchStatus.ERROR); + Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error")); + finishBuildingSessionsFromPEP(address); + } + } + }); + } catch (InvalidJidException e) { + Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Got address with invalid jid: " + address.getName()); + } + } + + @Override + public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) { + return findDevicesWithoutSession(conversation.getContact().getJid().toBareJid()); + } + + @Override + public Set<AxolotlAddress> findDevicesWithoutSession(final Jid contactJid) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Finding devices without session for " + contactJid); + Set<AxolotlAddress> addresses = new HashSet<>(); + if (deviceIds.get(contactJid) != null) { + for (Integer foreignId : this.deviceIds.get(contactJid)) { + AxolotlAddress address = new AxolotlAddress(contactJid.toString(), foreignId); + if (sessions.get(address) == null) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + if (identityKey != null) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache..."); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey); + sessions.put(address, session); + } else { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Found device " + contactJid + ":" + foreignId); + if (fetchStatusMap.get(address) != FetchStatus.ERROR) { + addresses.add(address); + } else { + Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken"); + } + } + } + } + } else { + Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Have no target devices in PEP!"); + } + if (deviceIds.get(account.getJid().toBareJid()) != null) { + for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) { + AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId); + if (sessions.get(address) == null) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + if (identityKey != null) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache..."); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey); + sessions.put(address, session); + } else { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId); + if (fetchStatusMap.get(address) != FetchStatus.ERROR) { + addresses.add(address); + } else { + Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken"); + } + } + } + } + } + + return addresses; + } + + @Override + public boolean createSessionsIfNeeded(final Conversation conversation) { + Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Creating axolotl sessions if needed..."); + boolean newSessions = false; + Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation); + for (AxolotlAddress address : addresses) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Processing device: " + address.toString()); + FetchStatus status = fetchStatusMap.get(address); + if (status == null || status == FetchStatus.TIMEOUT) { + fetchStatusMap.put(address, FetchStatus.PENDING); + this.buildSessionFromPEP(address); + newSessions = true; + } else if (status == FetchStatus.PENDING) { + newSessions = true; + } else { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Already fetching bundle for " + address.toString()); + } + } + + return newSessions; + } + + @Override + public boolean trustedSessionVerified(final Conversation conversation) { + Set<XmppAxolotlSession> sessions = findSessionsforContact(conversation.getContact()); + sessions.addAll(findOwnSessions()); + boolean verified = false; + for(XmppAxolotlSession session : sessions) { + if (session.getTrust().trusted()) { + if (session.getTrust() == XmppAxolotlSession.Trust.TRUSTED_X509) { + verified = true; + } else { + return false; + } + } + } + return verified; + } + + @Override + public boolean hasPendingKeyFetches(Account account, Contact contact) { + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0); + AxolotlAddress foreignAddress = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0); + return fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) + || fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING); + + } + + @Nullable + private XmppAxolotlMessage buildHeader(Contact contact) { + final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage( + contact.getJid().toBareJid(), getOwnDeviceId()); + + Set<XmppAxolotlSession> contactSessions = findSessionsforContact(contact); + Set<XmppAxolotlSession> ownSessions = findOwnSessions(); + if (contactSessions.isEmpty()) { + return null; + } + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Building axolotl foreign keyElements..."); + for (XmppAxolotlSession session : contactSessions) { + Log.v(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + session.getRemoteAddress().toString()); + axolotlMessage.addDevice(session); + } + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Building axolotl own keyElements..."); + for (XmppAxolotlSession session : ownSessions) { + Log.v(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + session.getRemoteAddress().toString()); + axolotlMessage.addDevice(session); + } + + return axolotlMessage; + } + + @Override + @Nullable + public XmppAxolotlMessage encrypt(Message message) { + XmppAxolotlMessage axolotlMessage = buildHeader(message.getContact()); + + if (axolotlMessage != null) { + final String content; + if (message.hasFileOnRemoteHost()) { + content = message.getFileParams().url.toString(); + } else { + content = message.getBody(); + } + try { + axolotlMessage.encrypt(content); + } catch (CryptoFailedException e) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage()); + return null; + } + } + + return axolotlMessage; + } + + @Override + public void preparePayloadMessage(final Message message, final boolean delay) { + executor.execute(new Runnable() { + @Override + public void run() { + XmppAxolotlMessage axolotlMessage = encrypt(message); + if (axolotlMessage == null) { + mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + //mXmppConnectionService.updateConversationUi(); + } else { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Generated message, caching: " + message.getUuid()); + messageCache.put(message.getUuid(), axolotlMessage); + mXmppConnectionService.resendMessage(message, delay); + } + } + }); + } + + @Override + public void prepareKeyTransportMessage(final Contact contact, final OnMessageCreatedCallback onMessageCreatedCallback) { + executor.execute(new Runnable() { + @Override + public void run() { + XmppAxolotlMessage axolotlMessage = buildHeader(contact); + onMessageCreatedCallback.run(axolotlMessage); + } + }); + } + + @Override + public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) { + XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid()); + if (axolotlMessage != null) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Cache hit: " + message.getUuid()); + messageCache.remove(message.getUuid()); + } else { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Cache miss: " + message.getUuid()); + } + return axolotlMessage; + } + + private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + return (identityKey != null) + ? new XmppAxolotlSession(account, axolotlStore, address, identityKey) + : null; + } + + private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) { + AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(), + message.getSenderDeviceId()); + XmppAxolotlSession session = sessions.get(senderAddress); + if (session == null) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message); + session = recreateUncachedSession(senderAddress); + if (session == null) { + session = new XmppAxolotlSession(account, axolotlStore, senderAddress); + } + } + return session; + } + + @Override + public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) { + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; + + XmppAxolotlSession session = getReceivingSession(message); + try { + plaintextMessage = message.decrypt(session, getOwnDeviceId()); + Integer preKeyId = session.getPreKeyId(); + if (preKeyId != null) { + publishBundlesIfNeeded(false, false); + session.resetPreKeyId(); + } + } catch (CryptoFailedException e) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage()); + } + + if (session.isFresh() && plaintextMessage != null) { + putFreshSession(session); + } + + return plaintextMessage; + } + + @Override + public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) { + XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage; + + XmppAxolotlSession session = getReceivingSession(message); + keyTransportMessage = message.getParameters(session, getOwnDeviceId()); + + if (session.isFresh() && keyTransportMessage != null) { + putFreshSession(session); + } + + return keyTransportMessage; + } + + private void putFreshSession(XmppAxolotlSession session) { + Log.d(Config.LOGTAG,"put fresh session"); + sessions.put(session); + if (Config.X509_VERIFICATION) { + if (session.getIdentityKey() != null) { + verifySessionWithPEP(session); + } else { + Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification"); + } + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceStub.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceStub.java new file mode 100644 index 00000000..a07905ab --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceStub.java @@ -0,0 +1,196 @@ +package de.thedevstack.conversationsplus.crypto.axolotl; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; + +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.Set; + +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Contact; +import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; + +/** + * Axolotl Service Stub implementation to avoid axolotl usage. + */ +public class AxolotlServiceStub implements AxolotlService { + + @Override + public boolean fetchMapHasErrors(Contact contact) { + return false; + } + + @Override + public String getOwnFingerprint() { + return null; + } + + @Override + public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) { + return Collections.emptySet(); + } + + @Override + public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact) { + return Collections.emptySet(); + } + + @Override + public long getNumTrustedKeys(Contact contact) { + return 0; + } + + @Override + public Set<String> getFingerprintsForOwnSessions() { + return Collections.emptySet(); + } + + @Override + public Set<String> getFingerprintsForContact(Contact contact) { + return Collections.emptySet(); + } + + @Override + public boolean isPepBroken() { + return true; + } + + @Override + public void regenerateKeys(boolean wipeOther) { + + } + + @Override + public int getOwnDeviceId() { + return 0; + } + + @Override + public Set<Integer> getOwnDeviceIds() { + return Collections.emptySet(); + } + + @Override + public void registerDevices(Jid jid, @NonNull Set<Integer> deviceIds) { + + } + + @Override + public void wipeOtherPepDevices() { + + } + + @Override + public void purgeKey(String fingerprint) { + + } + + @Override + public void publishOwnDeviceIdIfNeeded() { + + } + + @Override + public void publishOwnDeviceId(Set<Integer> deviceIds) { + + } + + @Override + public void publishDeviceVerificationAndBundle(SignedPreKeyRecord signedPreKeyRecord, Set<PreKeyRecord> preKeyRecords, boolean announceAfter, boolean wipe) { + + } + + @Override + public void publishBundlesIfNeeded(boolean announce, boolean wipe) { + + } + + @Override + public boolean isContactAxolotlCapable(Contact contact) { + return false; + } + + @Override + public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) { + return XmppAxolotlSession.Trust.TRUSTED; + } + + @Override + public X509Certificate getFingerprintCertificate(String fingerprint) { + return null; + } + + @Override + public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) { + + } + + @Override + public Set<AxolotlAddress> findDevicesWithoutSession(Conversation conversation) { + return Collections.emptySet(); + } + + @Override + public Set<AxolotlAddress> findDevicesWithoutSession(Jid contactJid) { + return Collections.emptySet(); + } + + @Override + public boolean createSessionsIfNeeded(Conversation conversation) { + return false; + } + + @Override + public boolean trustedSessionVerified(Conversation conversation) { + return false; + } + + @Override + public boolean hasPendingKeyFetches(Account account, Contact contact) { + return false; + } + + @Nullable + @Override + public XmppAxolotlMessage encrypt(Message message) { + return null; + } + + @Override + public void preparePayloadMessage(Message message, boolean delay) { + + } + + @Override + public void prepareKeyTransportMessage(Contact contact, OnMessageCreatedCallback onMessageCreatedCallback) { + + } + + @Override + public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) { + return null; + } + + @Override + public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) { + return null; + } + + @Override + public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) { + return null; + } + + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/CryptoFailedException.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/CryptoFailedException.java new file mode 100644 index 00000000..df281248 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/CryptoFailedException.java @@ -0,0 +1,7 @@ +package de.thedevstack.conversationsplus.crypto.axolotl; + +public class CryptoFailedException extends Exception { + public CryptoFailedException(Exception e){ + super(e); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/NoSessionsCreatedException.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/NoSessionsCreatedException.java new file mode 100644 index 00000000..bcb1c6a8 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/NoSessionsCreatedException.java @@ -0,0 +1,4 @@ +package de.thedevstack.conversationsplus.crypto.axolotl; + +public class NoSessionsCreatedException extends Throwable{ +} diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/OnMessageCreatedCallback.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/OnMessageCreatedCallback.java new file mode 100644 index 00000000..082b514b --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/OnMessageCreatedCallback.java @@ -0,0 +1,5 @@ +package de.thedevstack.conversationsplus.crypto.axolotl; + +public interface OnMessageCreatedCallback { + void run(XmppAxolotlMessage message); +} diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/SQLiteAxolotlStore.java new file mode 100644 index 00000000..35f630bb --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/SQLiteAxolotlStore.java @@ -0,0 +1,429 @@ +package de.thedevstack.conversationsplus.crypto.axolotl; + +import android.util.Log; +import android.util.LruCache; + +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.IdentityKeyPair; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.ecc.Curve; +import org.whispersystems.libaxolotl.ecc.ECKeyPair; +import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SessionRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; +import org.whispersystems.libaxolotl.util.KeyHelper; + +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Set; + +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.services.XmppConnectionService; + +public class SQLiteAxolotlStore implements AxolotlStore { + + public static final String PREKEY_TABLENAME = "prekeys"; + public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys"; + public static final String SESSION_TABLENAME = "sessions"; + public static final String IDENTITIES_TABLENAME = "identities"; + public static final String ACCOUNT = "account"; + public static final String DEVICE_ID = "device_id"; + public static final String ID = "id"; + public static final String KEY = "key"; + public static final String FINGERPRINT = "fingerprint"; + public static final String NAME = "name"; + public static final String TRUSTED = "trusted"; + public static final String OWN = "ownkey"; + public static final String CERTIFICATE = "certificate"; + + public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id"; + public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id"; + + private static final int NUM_TRUSTS_TO_CACHE = 100; + + private final Account account; + private final XmppConnectionService mXmppConnectionService; + + private IdentityKeyPair identityKeyPair; + private int localRegistrationId; + private int currentPreKeyId = 0; + + private final LruCache<String, XmppAxolotlSession.Trust> trustCache = + new LruCache<String, XmppAxolotlSession.Trust>(NUM_TRUSTS_TO_CACHE) { + @Override + protected XmppAxolotlSession.Trust create(String fingerprint) { + return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint); + } + }; + + private static IdentityKeyPair generateIdentityKeyPair() { + Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl IdentityKeyPair..."); + ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); + return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), + identityKeyPairKeys.getPrivateKey()); + } + + private static int generateRegistrationId() { + Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl registration ID..."); + return KeyHelper.generateRegistrationId(true); + } + + public SQLiteAxolotlStore(Account account, XmppConnectionService service) { + this.account = account; + this.mXmppConnectionService = service; + this.localRegistrationId = loadRegistrationId(); + this.currentPreKeyId = loadCurrentPreKeyId(); + for (SignedPreKeyRecord record : loadSignedPreKeys()) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId()); + } + } + + public int getCurrentPreKeyId() { + return currentPreKeyId; + } + + // -------------------------------------- + // IdentityKeyStore + // -------------------------------------- + + private IdentityKeyPair loadIdentityKeyPair() { + IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account); + + if (ownKey != null) { + return ownKey; + } else { + Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Could not retrieve own IdentityKeyPair"); + ownKey = generateIdentityKeyPair(); + mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownKey); + } + return ownKey; + } + + private int loadRegistrationId() { + return loadRegistrationId(false); + } + + private int loadRegistrationId(boolean regenerate) { + String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID); + int reg_id; + if (!regenerate && regIdString != null) { + reg_id = Integer.valueOf(regIdString); + } else { + Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid()); + reg_id = generateRegistrationId(); + boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id)); + if (success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Failed to write new key to the database!"); + } + } + return reg_id; + } + + private int loadCurrentPreKeyId() { + String regIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID); + int reg_id; + if (regIdString != null) { + reg_id = Integer.valueOf(regIdString); + } else { + Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid()); + reg_id = 0; + } + return reg_id; + } + + public void regenerate() { + mXmppConnectionService.databaseBackend.wipeAxolotlDb(account); + trustCache.evictAll(); + account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0)); + identityKeyPair = loadIdentityKeyPair(); + localRegistrationId = loadRegistrationId(true); + currentPreKeyId = 0; + mXmppConnectionService.updateAccountUi(); + } + + /** + * Get the local client's identity key pair. + * + * @return The local client's persistent identity key pair. + */ + @Override + public IdentityKeyPair getIdentityKeyPair() { + if (identityKeyPair == null) { + identityKeyPair = loadIdentityKeyPair(); + } + return identityKeyPair; + } + + /** + * Return the local client's registration ID. + * <p/> + * Clients should maintain a registration ID, a random number + * between 1 and 16380 that's generated once at install time. + * + * @return the local client's registration ID. + */ + @Override + public int getLocalRegistrationId() { + return localRegistrationId; + } + + /** + * Save a remote client's identity key + * <p/> + * Store a remote client's identity key as trusted. + * + * @param name The name of the remote client. + * @param identityKey The remote client's identity key. + */ + @Override + public void saveIdentity(String name, IdentityKey identityKey) { + if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) { + mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey); + } + } + + /** + * Verify a remote client's identity key. + * <p/> + * Determine whether a remote client's identity is trusted. Convention is + * that the TextSecure protocol is 'trust on first use.' This means that + * an identity key is considered 'trusted' if there is no entry for the recipient + * in the local store, or if it matches the saved key for a recipient in the local + * store. Only if it mismatches an entry in the local store is it considered + * 'untrusted.' + * + * @param name The name of the remote client. + * @param identityKey The identity key to verify. + * @return true if trusted, false if untrusted. + */ + @Override + public boolean isTrustedIdentity(String name, IdentityKey identityKey) { + return true; + } + + public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) { + return (fingerprint == null)? null : trustCache.get(fingerprint); + } + + public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) { + mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust); + trustCache.remove(fingerprint); + } + + public void setFingerprintCertificate(String fingerprint, X509Certificate x509Certificate) { + mXmppConnectionService.databaseBackend.setIdentityKeyCertificate(account, fingerprint, x509Certificate); + } + + public X509Certificate getFingerprintCertificate(String fingerprint) { + return mXmppConnectionService.databaseBackend.getIdentityKeyCertifcate(account, fingerprint); + } + + public Set<IdentityKey> getContactKeysWithTrust(String bareJid, XmppAxolotlSession.Trust trust) { + return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust); + } + + public long getContactNumTrustedKeys(String bareJid) { + return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid); + } + + // -------------------------------------- + // SessionStore + // -------------------------------------- + + /** + * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple, + * or a new SessionRecord if one does not currently exist. + * <p/> + * It is important that implementations return a copy of the current durable information. The + * returned SessionRecord may be modified, but those changes should not have an effect on the + * durable session state (what is returned by subsequent calls to this method) without the + * store method being called here first. + * + * @param address The name and device ID of the remote client. + * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or + * a new SessionRecord if one does not currently exist. + */ + @Override + public SessionRecord loadSession(AxolotlAddress address) { + SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address); + return (session != null) ? session : new SessionRecord(); + } + + /** + * Returns all known devices with active sessions for a recipient + * + * @param name the name of the client. + * @return all known sub-devices with active sessions. + */ + @Override + public List<Integer> getSubDeviceSessions(String name) { + return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account, + new AxolotlAddress(name, 0)); + } + + /** + * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple. + * + * @param address the address of the remote client. + * @param record the current SessionRecord for the remote client. + */ + @Override + public void storeSession(AxolotlAddress address, SessionRecord record) { + mXmppConnectionService.databaseBackend.storeSession(account, address, record); + } + + /** + * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple. + * + * @param address the address of the remote client. + * @return true if a {@link SessionRecord} exists, false otherwise. + */ + @Override + public boolean containsSession(AxolotlAddress address) { + return mXmppConnectionService.databaseBackend.containsSession(account, address); + } + + /** + * Remove a {@link SessionRecord} for a recipientId + deviceId tuple. + * + * @param address the address of the remote client. + */ + @Override + public void deleteSession(AxolotlAddress address) { + mXmppConnectionService.databaseBackend.deleteSession(account, address); + } + + /** + * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId. + * + * @param name the name of the remote client. + */ + @Override + public void deleteAllSessions(String name) { + AxolotlAddress address = new AxolotlAddress(name, 0); + mXmppConnectionService.databaseBackend.deleteAllSessions(account, + address); + } + + // -------------------------------------- + // PreKeyStore + // -------------------------------------- + + /** + * Load a local PreKeyRecord. + * + * @param preKeyId the ID of the local PreKeyRecord. + * @return the corresponding PreKeyRecord. + * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord. + */ + @Override + public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { + PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId); + if (record == null) { + throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId); + } + return record; + } + + /** + * Store a local PreKeyRecord. + * + * @param preKeyId the ID of the PreKeyRecord to store. + * @param record the PreKeyRecord. + */ + @Override + public void storePreKey(int preKeyId, PreKeyRecord record) { + mXmppConnectionService.databaseBackend.storePreKey(account, record); + currentPreKeyId = preKeyId; + boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId)); + if (success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Failed to write new prekey id to the database!"); + } + } + + /** + * @param preKeyId A PreKeyRecord ID. + * @return true if the store has a record for the preKeyId, otherwise false. + */ + @Override + public boolean containsPreKey(int preKeyId) { + return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId); + } + + /** + * Delete a PreKeyRecord from local storage. + * + * @param preKeyId The ID of the PreKeyRecord to remove. + */ + @Override + public void removePreKey(int preKeyId) { + mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId); + } + + // -------------------------------------- + // SignedPreKeyStore + // -------------------------------------- + + /** + * Load a local SignedPreKeyRecord. + * + * @param signedPreKeyId the ID of the local SignedPreKeyRecord. + * @return the corresponding SignedPreKeyRecord. + * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord. + */ + @Override + public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { + SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId); + if (record == null) { + throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId); + } + return record; + } + + /** + * Load all local SignedPreKeyRecords. + * + * @return All stored SignedPreKeyRecords. + */ + @Override + public List<SignedPreKeyRecord> loadSignedPreKeys() { + return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account); + } + + /** + * Store a local SignedPreKeyRecord. + * + * @param signedPreKeyId the ID of the SignedPreKeyRecord to store. + * @param record the SignedPreKeyRecord. + */ + @Override + public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { + mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record); + } + + /** + * @param signedPreKeyId A SignedPreKeyRecord ID. + * @return true if the store has a record for the signedPreKeyId, otherwise false. + */ + @Override + public boolean containsSignedPreKey(int signedPreKeyId) { + return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId); + } + + /** + * Delete a SignedPreKeyRecord from local storage. + * + * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove. + */ + @Override + public void removeSignedPreKey(int signedPreKeyId) { + mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/XmppAxolotlMessage.java new file mode 100644 index 00000000..837b601c --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/XmppAxolotlMessage.java @@ -0,0 +1,249 @@ +package de.thedevstack.conversationsplus.crypto.axolotl; + +import android.util.Base64; +import android.util.Log; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; + +public class XmppAxolotlMessage { + public static final String CONTAINERTAG = "encrypted"; + public static final String HEADER = "header"; + public static final String SOURCEID = "sid"; + public static final String KEYTAG = "key"; + public static final String REMOTEID = "rid"; + public static final String IVTAG = "iv"; + public static final String PAYLOAD = "payload"; + + private static final String KEYTYPE = "AES"; + private static final String CIPHERMODE = "AES/GCM/NoPadding"; + private static final String PROVIDER = "BC"; + + private byte[] innerKey; + private byte[] ciphertext = null; + private byte[] iv = null; + private final Map<Integer, byte[]> keys; + private final Jid from; + private final int sourceDeviceId; + + public static class XmppAxolotlPlaintextMessage { + private final String plaintext; + private final String fingerprint; + + public XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) { + this.plaintext = plaintext; + this.fingerprint = fingerprint; + } + + public String getPlaintext() { + return plaintext; + } + + + public String getFingerprint() { + return fingerprint; + } + } + + public static class XmppAxolotlKeyTransportMessage { + private final String fingerprint; + private final byte[] key; + private final byte[] iv; + + public XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) { + this.fingerprint = fingerprint; + this.key = key; + this.iv = iv; + } + + public String getFingerprint() { + return fingerprint; + } + + public byte[] getKey() { + return key; + } + + public byte[] getIv() { + return iv; + } + } + + private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException { + this.from = from; + Element header = axolotlMessage.findChild(HEADER); + this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID)); + List<Element> keyElements = header.getChildren(); + this.keys = new HashMap<>(keyElements.size()); + for (Element keyElement : keyElements) { + switch (keyElement.getName()) { + case KEYTAG: + try { + Integer recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); + byte[] key = Base64.decode(keyElement.getContent(), Base64.DEFAULT); + this.keys.put(recipientId, key); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(e); + } + break; + case IVTAG: + if (this.iv != null) { + throw new IllegalArgumentException("Duplicate iv entry"); + } + iv = Base64.decode(keyElement.getContent(), Base64.DEFAULT); + break; + default: + Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement.toString()); + break; + } + } + Element payloadElement = axolotlMessage.findChild(PAYLOAD); + if (payloadElement != null) { + ciphertext = Base64.decode(payloadElement.getContent(), Base64.DEFAULT); + } + } + + public XmppAxolotlMessage(Jid from, int sourceDeviceId) { + this.from = from; + this.sourceDeviceId = sourceDeviceId; + this.keys = new HashMap<>(); + this.iv = generateIv(); + this.innerKey = generateKey(); + } + + public static XmppAxolotlMessage fromElement(Element element, Jid from) { + return new XmppAxolotlMessage(element, from); + } + + private static byte[] generateKey() { + try { + KeyGenerator generator = KeyGenerator.getInstance(KEYTYPE); + generator.init(128); + return generator.generateKey().getEncoded(); + } catch (NoSuchAlgorithmException e) { + Log.e(Config.LOGTAG, e.getMessage()); + return null; + } + } + + private static byte[] generateIv() { + SecureRandom random = new SecureRandom(); + byte[] iv = new byte[16]; + random.nextBytes(iv); + return iv; + } + + public void encrypt(String plaintext) throws CryptoFailedException { + try { + SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); + this.innerKey = secretKey.getEncoded(); + this.ciphertext = cipher.doFinal(plaintext.getBytes()); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException + | InvalidAlgorithmParameterException e) { + throw new CryptoFailedException(e); + } + } + + public Jid getFrom() { + return this.from; + } + + public int getSenderDeviceId() { + return sourceDeviceId; + } + + public byte[] getCiphertext() { + return ciphertext; + } + + public void addDevice(XmppAxolotlSession session) { + byte[] key = session.processSending(innerKey); + if (key != null) { + keys.put(session.getRemoteAddress().getDeviceId(), key); + } + } + + public byte[] getInnerKey() { + return innerKey; + } + + public byte[] getIV() { + return this.iv; + } + + public Element toElement() { + Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX); + Element headerElement = encryptionElement.addChild(HEADER); + headerElement.setAttribute(SOURCEID, sourceDeviceId); + for (Map.Entry<Integer, byte[]> keyEntry : keys.entrySet()) { + Element keyElement = new Element(KEYTAG); + keyElement.setAttribute(REMOTEID, keyEntry.getKey()); + keyElement.setContent(Base64.encodeToString(keyEntry.getValue(), Base64.DEFAULT)); + headerElement.addChild(keyElement); + } + headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT)); + if (ciphertext != null) { + Element payload = encryptionElement.addChild(PAYLOAD); + payload.setContent(Base64.encodeToString(ciphertext, Base64.DEFAULT)); + } + return encryptionElement; + } + + private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) { + byte[] encryptedKey = keys.get(sourceDeviceId); + return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null; + } + + public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) { + byte[] key = unpackKey(session, sourceDeviceId); + return (key != null) + ? new XmppAxolotlKeyTransportMessage(session.getFingerprint(), key, getIV()) + : null; + } + + public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { + XmppAxolotlPlaintextMessage plaintextMessage = null; + byte[] key = unpackKey(session, sourceDeviceId); + if (key != null) { + try { + Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); + SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + + String plaintext = new String(cipher.doFinal(ciphertext)); + plaintextMessage = new XmppAxolotlPlaintextMessage(plaintext, session.getFingerprint()); + + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | InvalidAlgorithmParameterException | IllegalBlockSizeException + | BadPaddingException | NoSuchProviderException e) { + throw new CryptoFailedException(e); + } + } + return plaintextMessage; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/XmppAxolotlSession.java new file mode 100644 index 00000000..47b2133a --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/XmppAxolotlSession.java @@ -0,0 +1,221 @@ +package de.thedevstack.conversationsplus.crypto.axolotl; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.DuplicateMessageException; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.InvalidMessageException; +import org.whispersystems.libaxolotl.InvalidVersionException; +import org.whispersystems.libaxolotl.LegacyMessageException; +import org.whispersystems.libaxolotl.NoSessionException; +import org.whispersystems.libaxolotl.SessionCipher; +import org.whispersystems.libaxolotl.UntrustedIdentityException; +import org.whispersystems.libaxolotl.protocol.CiphertextMessage; +import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; +import org.whispersystems.libaxolotl.protocol.WhisperMessage; + +import java.util.HashMap; +import java.util.Map; + +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.entities.Account; + +public class XmppAxolotlSession { + private final SessionCipher cipher; + private final SQLiteAxolotlStore sqLiteAxolotlStore; + private final AxolotlAddress remoteAddress; + private final Account account; + private IdentityKey identityKey; + private Integer preKeyId = null; + private boolean fresh = true; + + public enum Trust { + UNDECIDED(0), + TRUSTED(1), + UNTRUSTED(2), + COMPROMISED(3), + INACTIVE_TRUSTED(4), + INACTIVE_UNDECIDED(5), + INACTIVE_UNTRUSTED(6), + TRUSTED_X509(7), + INACTIVE_TRUSTED_X509(8); + + private static final Map<Integer, Trust> trustsByValue = new HashMap<>(); + + static { + for (Trust trust : Trust.values()) { + trustsByValue.put(trust.getCode(), trust); + } + } + + private final int code; + + Trust(int code) { + this.code = code; + } + + public int getCode() { + return this.code; + } + + public String toString() { + switch (this) { + case UNDECIDED: + return "Trust undecided " + getCode(); + case TRUSTED: + return "Trusted " + getCode(); + case COMPROMISED: + return "Compromised " + getCode(); + case INACTIVE_TRUSTED: + return "Inactive (Trusted)" + getCode(); + case INACTIVE_UNDECIDED: + return "Inactive (Undecided)" + getCode(); + case INACTIVE_UNTRUSTED: + return "Inactive (Untrusted)" + getCode(); + case TRUSTED_X509: + return "Trusted (X509) " + getCode(); + case INACTIVE_TRUSTED_X509: + return "Inactive (Trusted (X509)) " + getCode(); + case UNTRUSTED: + default: + return "Untrusted " + getCode(); + } + } + + public static Trust fromBoolean(Boolean trusted) { + return trusted ? TRUSTED : UNTRUSTED; + } + + public static Trust fromCode(int code) { + return trustsByValue.get(code); + } + + public boolean trusted() { + return this == TRUSTED_X509 || this == TRUSTED; + } + + public boolean trustedInactive() { + return this == INACTIVE_TRUSTED_X509 || this == INACTIVE_TRUSTED; + } + } + + public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, IdentityKey identityKey) { + this(account, store, remoteAddress); + this.identityKey = identityKey; + } + + public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) { + this.cipher = new SessionCipher(store, remoteAddress); + this.remoteAddress = remoteAddress; + this.sqLiteAxolotlStore = store; + this.account = account; + } + + public Integer getPreKeyId() { + return preKeyId; + } + + public void resetPreKeyId() { + + preKeyId = null; + } + + public String getFingerprint() { + return identityKey == null ? null : identityKey.getFingerprint().replaceAll("\\s", ""); + } + + public IdentityKey getIdentityKey() { + return identityKey; + } + + public AxolotlAddress getRemoteAddress() { + return remoteAddress; + } + + public boolean isFresh() { + return fresh; + } + + public void setNotFresh() { + this.fresh = false; + } + + protected void setTrust(Trust trust) { + sqLiteAxolotlStore.setFingerprintTrust(getFingerprint(), trust); + } + + protected Trust getTrust() { + Trust trust = sqLiteAxolotlStore.getFingerprintTrust(getFingerprint()); + return (trust == null) ? Trust.UNDECIDED : trust; + } + + @Nullable + public byte[] processReceiving(byte[] encryptedKey) { + byte[] plaintext = null; + Trust trust = getTrust(); + switch (trust) { + case INACTIVE_TRUSTED: + case UNDECIDED: + case UNTRUSTED: + case TRUSTED: + case INACTIVE_TRUSTED_X509: + case TRUSTED_X509: + try { + try { + PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey); + Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); + IdentityKey msgIdentityKey = message.getIdentityKey(); + if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) { + Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint()); + } else { + this.identityKey = msgIdentityKey; + plaintext = cipher.decrypt(message); + if (message.getPreKeyId().isPresent()) { + preKeyId = message.getPreKeyId().get(); + } + } + } catch (InvalidMessageException | InvalidVersionException e) { + Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "WhisperMessage received"); + WhisperMessage message = new WhisperMessage(encryptedKey); + plaintext = cipher.decrypt(message); + } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { + Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); + } + } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) { + Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); + } + + if (plaintext != null) { + if (trust == Trust.INACTIVE_TRUSTED) { + setTrust(Trust.TRUSTED); + } else if (trust == Trust.INACTIVE_TRUSTED_X509) { + setTrust(Trust.TRUSTED_X509); + } + } + + break; + + case COMPROMISED: + default: + // ignore + break; + } + return plaintext; + } + + @Nullable + public byte[] processSending(@NonNull byte[] outgoingMessage) { + Trust trust = getTrust(); + if (trust.trusted()) { + CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); + return ciphertextMessage.serialize(); + } else { + return null; + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/External.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/External.java new file mode 100644 index 00000000..dc50cbbe --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/External.java @@ -0,0 +1,30 @@ +package de.thedevstack.conversationsplus.crypto.sasl; + +import android.util.Base64; + +import java.security.SecureRandom; + +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.xml.TagWriter; + +public class External extends SaslMechanism { + + public External(TagWriter tagWriter, Account account, SecureRandom rng) { + super(tagWriter, account, rng); + } + + @Override + public int getPriority() { + return 25; + } + + @Override + public String getMechanism() { + return "EXTERNAL"; + } + + @Override + public String getClientFirstMessage() { + return Base64.encodeToString(account.getJid().toBareJid().toString().getBytes(),Base64.NO_WRAP); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/SaslMechanism.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/SaslMechanism.java index ed2764c8..1f6d2bde 100644 --- a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/SaslMechanism.java +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/SaslMechanism.java @@ -11,7 +11,7 @@ public abstract class SaslMechanism { final protected Account account; final protected SecureRandom rng; - protected static enum State { + protected enum State { INITIAL, AUTH_TEXT_SENT, RESPONSE_SENT, diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/ScramSha1.java b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/ScramSha1.java index a991b1d7..3540d2cf 100644 --- a/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/ScramSha1.java +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/sasl/ScramSha1.java @@ -21,7 +21,6 @@ public class ScramSha1 extends SaslMechanism { // TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage. final private static String GS2_HEADER = "n,,"; private String clientFirstMessageBare; - private byte[] serverFirstMessage; final private String clientNonce; private byte[] serverSignature = null; private static HMac HMAC; @@ -101,7 +100,10 @@ public class ScramSha1 extends SaslMechanism { public String getResponse(final String challenge) throws AuthenticationException { switch (state) { case AUTH_TEXT_SENT: - serverFirstMessage = Base64.decode(challenge, Base64.DEFAULT); + if (challenge == null) { + throw new AuthenticationException("challenge can not be null"); + } + byte[] serverFirstMessage = Base64.decode(challenge, Base64.DEFAULT); final Tokenizer tokenizer = new Tokenizer(serverFirstMessage); String nonce = ""; int iterationCount = -1; diff --git a/src/main/java/de/thedevstack/conversationsplus/dto/SrvRecord.java b/src/main/java/de/thedevstack/conversationsplus/dto/SrvRecord.java index 3bc79c4f..1e0eebc7 100644 --- a/src/main/java/de/thedevstack/conversationsplus/dto/SrvRecord.java +++ b/src/main/java/de/thedevstack/conversationsplus/dto/SrvRecord.java @@ -8,6 +8,7 @@ public class SrvRecord implements Comparable<SrvRecord> { private int priority; private String name; private int port; + private boolean useTls = false; public SrvRecord(int priority, String name, int port) { this.priority = priority; @@ -15,6 +16,13 @@ public class SrvRecord implements Comparable<SrvRecord> { this.port = port; } + public SrvRecord(int priority, String name, int port, boolean useTls) { + this.priority = priority; + this.name = name; + this.port = port; + this.useTls = useTls; + } + /** * Compares this record to the specified record to determine their relative * order. @@ -50,4 +58,8 @@ public class SrvRecord implements Comparable<SrvRecord> { public int getPriority() { return priority; } + + public boolean isUseTls() { + return useTls; + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Account.java b/src/main/java/de/thedevstack/conversationsplus/entities/Account.java index 8d879fa0..f7dee013 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Account.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Account.java @@ -3,6 +3,7 @@ package de.thedevstack.conversationsplus.entities; import android.content.ContentValues; import android.database.Cursor; import android.os.SystemClock; +import android.util.Pair; import net.java.otr4j.crypto.OtrCryptoEngineImpl; import net.java.otr4j.crypto.OtrCryptoException; @@ -13,13 +14,19 @@ import org.json.JSONObject; import java.security.PublicKey; import java.security.interfaces.DSAPublicKey; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; +import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.crypto.OtrService; +import de.thedevstack.conversationsplus.crypto.PgpDecryptionService; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlServiceImpl; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlServiceStub; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.xmpp.XmppConnection; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; @@ -36,6 +43,9 @@ public class Account extends AbstractEntity { public static final String ROSTERVERSION = "rosterversion"; public static final String KEYS = "keys"; public static final String AVATAR = "avatar"; + public static final String DISPLAY_NAME = "display_name"; + public static final String HOSTNAME = "hostname"; + public static final String PORT = "port"; public static final String PINNED_MECHANISM_KEY = "pinned_mechanism"; @@ -43,12 +53,29 @@ public class Account extends AbstractEntity { public static final int OPTION_DISABLED = 1; public static final int OPTION_REGISTER = 2; public static final int OPTION_USECOMPRESSION = 3; + public final HashSet<Pair<String, String>> inProgressDiscoFetches = new HashSet<>(); public boolean httpUploadAvailable() { return xmppConnection != null && xmppConnection.getFeatures().httpUpload(); } - public static enum State { + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + public XmppConnection.Identity getServerIdentity() { + if (xmppConnection == null) { + return XmppConnection.Identity.UNKNOWN; + } else { + return xmppConnection.getServerIdentity(); + } + } + + public enum State { DISABLED, OFFLINE, CONNECTING, @@ -61,7 +88,8 @@ public class Account extends AbstractEntity { REGISTRATION_SUCCESSFUL, REGISTRATION_NOT_SUPPORTED(true), SECURITY_ERROR(true), - INCOMPATIBLE_SERVER(true); + INCOMPATIBLE_SERVER(true), + TOR_NOT_AVAILABLE(true); private final boolean isError; @@ -105,6 +133,8 @@ public class Account extends AbstractEntity { return R.string.account_status_security_error; case INCOMPATIBLE_SERVER: return R.string.account_status_incompatible_server; + case TOR_NOT_AVAILABLE: + return R.string.account_status_tor_unavailable; default: return R.string.account_status_unknown; } @@ -113,6 +143,10 @@ public class Account extends AbstractEntity { public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<>(); public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<>(); + + private static final String KEY_PGP_SIGNATURE = "pgp_signature"; + private static final String KEY_PGP_ID = "pgp_id"; + protected Jid jid; protected String password; protected int options = 0; @@ -120,15 +154,19 @@ public class Account extends AbstractEntity { protected State status = State.OFFLINE; protected JSONObject keys = new JSONObject(); protected String avatar; + protected String displayName = null; + protected String hostname = null; + protected int port = 5222; protected boolean online = false; private OtrService mOtrService = null; + private AxolotlService axolotlService = null; + private PgpDecryptionService pgpDecryptionService = null; private XmppConnection xmppConnection = null; private long mEndGracePeriod = 0L; private String otrFingerprint; private final Roster roster = new Roster(this); private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>(); private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>(); - private XmppConnectionService mXmppConnectionService; public Account() { this.uuid = "0"; @@ -136,12 +174,12 @@ public class Account extends AbstractEntity { public Account(final Jid jid, final String password) { this(java.util.UUID.randomUUID().toString(), jid, - password, 0, null, "", null); + password, 0, null, "", null, null, null, 5222); } - public Account(final String uuid, final Jid jid, + private Account(final String uuid, final Jid jid, final String password, final int options, final String rosterVersion, final String keys, - final String avatar) { + final String avatar, String displayName, String hostname, int port) { this.uuid = uuid; this.jid = jid; if (jid.isBareJid()) { @@ -156,6 +194,9 @@ public class Account extends AbstractEntity { this.keys = new JSONObject(); } this.avatar = avatar; + this.displayName = displayName; + this.hostname = hostname; + this.port = port; } public static Account fromCursor(final Cursor cursor) { @@ -171,7 +212,10 @@ public class Account extends AbstractEntity { cursor.getInt(cursor.getColumnIndex(OPTIONS)), cursor.getString(cursor.getColumnIndex(ROSTERVERSION)), cursor.getString(cursor.getColumnIndex(KEYS)), - cursor.getString(cursor.getColumnIndex(AVATAR))); + cursor.getString(cursor.getColumnIndex(AVATAR)), + cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)), + cursor.getString(cursor.getColumnIndex(HOSTNAME)), + cursor.getInt(cursor.getColumnIndex(PORT))); } public boolean isOptionSet(final int option) { @@ -190,18 +234,14 @@ public class Account extends AbstractEntity { return jid.getLocalpart(); } - public void setUsername(final String username) throws InvalidJidException { - jid = Jid.fromParts(username, jid.getDomainpart(), jid.getResourcepart()); + public void setJid(final Jid jid) { + this.jid = jid; } public Jid getServer() { return jid.toDomainJid(); } - public void setServer(final String server) throws InvalidJidException { - jid = Jid.fromParts(jid.getLocalpart(), server, jid.getResourcepart()); - } - public String getPassword() { return password; } @@ -210,6 +250,22 @@ public class Account extends AbstractEntity { this.password = password; } + public void setHostname(String hostname) { + this.hostname = hostname; + } + + public String getHostname() { + return this.hostname == null ? "" : this.hostname; + } + + public void setPort(int port) { + this.port = port; + } + + public int getPort() { + return this.port; + } + public State getStatus() { if (isOptionSet(OPTION_DISABLED)) { return State.DISABLED; @@ -227,7 +283,7 @@ public class Account extends AbstractEntity { } public boolean hasErrorStatus() { - return getXmppConnection() != null && getStatus().isError() && getXmppConnection().getAttempt() >= 2; + return getXmppConnection() != null && getStatus().isError() && getXmppConnection().getAttempt() >= 3; } public String getResource() { @@ -255,6 +311,10 @@ public class Account extends AbstractEntity { return keys; } + public String getKey(final String name) { + return this.keys.optString(name, null); + } + public boolean setKey(final String keyName, final String keyValue) { try { this.keys.put(keyName, keyValue); @@ -264,6 +324,14 @@ public class Account extends AbstractEntity { } } + public boolean setPrivateKeyAlias(String alias) { + return setKey("private_key_alias", alias); + } + + public String getPrivateKeyAlias() { + return getKey("private_key_alias"); + } + @Override public ContentValues getContentValues() { final ContentValues values = new ContentValues(); @@ -275,18 +343,38 @@ public class Account extends AbstractEntity { values.put(KEYS, this.keys.toString()); values.put(ROSTERVERSION, rosterVersion); values.put(AVATAR, avatar); + values.put(DISPLAY_NAME, displayName); + values.put(HOSTNAME, hostname); + values.put(PORT, port); return values; } + public AxolotlService getAxolotlService() { + return axolotlService; + } + public void initAccountServices(final XmppConnectionService context) { - this.mXmppConnectionService = context; this.mOtrService = new OtrService(context, this); + if (ConversationsPlusPreferences.omemoEnabled()) { + this.axolotlService = new AxolotlServiceImpl(this, context); + if (xmppConnection != null) { + xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService); + } + } else { + this.axolotlService = new AxolotlServiceStub(); + } + + this.pgpDecryptionService = new PgpDecryptionService(context); } public OtrService getOtrService() { return this.mOtrService; } + public PgpDecryptionService getPgpDecryptionService() { + return pgpDecryptionService; + } + public XmppConnection getXmppConnection() { return this.xmppConnection; } @@ -332,17 +420,56 @@ public class Account extends AbstractEntity { } public String getPgpSignature() { - if (keys.has("pgp_signature")) { - try { - return keys.getString("pgp_signature"); - } catch (final JSONException e) { + try { + if (keys.has(KEY_PGP_SIGNATURE) && !"null".equals(keys.getString(KEY_PGP_SIGNATURE))) { + return keys.getString(KEY_PGP_SIGNATURE); + } else { return null; } - } else { + } catch (final JSONException e) { return null; } } + public boolean setPgpSignature(String signature) { + try { + keys.put(KEY_PGP_SIGNATURE, signature); + } catch (JSONException e) { + return false; + } + return true; + } + + public boolean unsetPgpSignature() { + try { + keys.put(KEY_PGP_SIGNATURE, JSONObject.NULL); + } catch (JSONException e) { + return false; + } + return true; + } + + public long getPgpId() { + if (keys.has(KEY_PGP_ID)) { + try { + return keys.getLong(KEY_PGP_ID); + } catch (JSONException e) { + return -1; + } + } else { + return -1; + } + } + + public boolean setPgpSignId(long pgpID) { + try { + keys.put(KEY_PGP_ID, pgpID); + } catch (JSONException e) { + return false; + } + return true; + } + public Roster getRoster() { return this.roster; } @@ -420,8 +547,4 @@ public class Account extends AbstractEntity { public boolean isOnlineAndConnected() { return this.getStatus() == State.ONLINE && this.getXmppConnection() != null; } - - public XmppConnectionService getXmppConnectionService() { - return mXmppConnectionService; - } } diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Bookmark.java b/src/main/java/de/thedevstack/conversationsplus/entities/Bookmark.java index 9e67bf2d..07a77eae 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Bookmark.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Bookmark.java @@ -2,12 +2,11 @@ package de.thedevstack.conversationsplus.entities; import android.graphics.Color; -import android.graphics.Color; - import java.util.ArrayList; import java.util.List; import java.util.Locale; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.utils.UIHelper; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.jid.Jid; @@ -54,14 +53,26 @@ public class Bookmark extends Element implements ListItem { if (this.mJoinedConversation != null && (this.mJoinedConversation.getMucOptions().getSubject() != null)) { return this.mJoinedConversation.getMucOptions().getSubject(); - } else if (getName() != null) { - return getName(); + } else if (getBookmarkName() != null) { + return getBookmarkName(); } else { return this.getJid().getLocalpart(); } } @Override + public String getDisplayJid() { + Jid jid = getJid(); + if (Config.LOCK_DOMAINS_IN_CONVERSATIONS && jid != null && jid.getDomainpart().equals(Config.CONFERENCE_DOMAIN_LOCK)) { + return jid.getLocalpart(); + } else if (jid != null) { + return jid.toString(); + } else { + return null; + } + } + + @Override public Jid getJid() { return this.getAttributeAsJid("jid"); } @@ -143,12 +154,18 @@ public class Bookmark extends Element implements ListItem { this.mJoinedConversation = conversation; } - public String getName() { + public String getBookmarkName() { return this.getAttribute("name"); } - public void setName(String name) { - this.name = name; + public boolean setBookmarkName(String name) { + String before = getBookmarkName(); + if (name != null && !name.equals(before)) { + this.setAttribute("name", name); + return true; + } else { + return false; + } } public void unregisterConversation() { diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Contact.java b/src/main/java/de/thedevstack/conversationsplus/entities/Contact.java index ca734403..74d76f13 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Contact.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Contact.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.utils.UIHelper; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; @@ -38,13 +39,14 @@ public class Contact implements ListItem, Blockable { protected String systemName; protected String serverName; protected String presenceName; + protected String commonName; protected Jid jid; protected int subscription = 0; protected String systemAccount; protected String photoUri; protected JSONObject keys = new JSONObject(); protected JSONArray groups = new JSONArray(); - protected Presences presences = new Presences(); + protected final Presences presences = new Presences(); protected Account account; protected Avatar avatar; @@ -105,7 +107,9 @@ public class Contact implements ListItem, Blockable { } public String getDisplayName() { - if (this.systemName != null) { + if (this.commonName != null && Config.X509_VERIFICATION) { + return this.commonName; + } else if (this.systemName != null) { return this.systemName; } else if (this.serverName != null) { return this.serverName; @@ -118,6 +122,17 @@ public class Contact implements ListItem, Blockable { } } + @Override + public String getDisplayJid() { + if (Config.LOCK_DOMAINS_IN_CONVERSATIONS && jid != null && jid.getDomainpart().equals(Config.DOMAIN_LOCK)) { + return jid.getLocalpart(); + } else if (jid != null) { + return jid.toString(); + } else { + return null; + } + } + public String getProfilePhoto() { return this.photoUri; } @@ -133,17 +148,17 @@ public class Contact implements ListItem, Blockable { tags.add(new Tag(group, UIHelper.getColorForName(group))); } switch (getMostAvailableStatus()) { - case Presences.CHAT: - case Presences.ONLINE: + case CHAT: + case ONLINE: tags.add(new Tag("online", 0xff259b24)); break; - case Presences.AWAY: + case AWAY: tags.add(new Tag("away", 0xffff9800)); break; - case Presences.XA: + case XA: tags.add(new Tag("not available", 0xfff44336)); break; - case Presences.DND: + case DND: tags.add(new Tag("dnd", 0xfff44336)); break; } @@ -189,20 +204,22 @@ public class Contact implements ListItem, Blockable { } public ContentValues getContentValues() { - final ContentValues values = new ContentValues(); - values.put(ACCOUNT, accountUuid); - values.put(SYSTEMNAME, systemName); - values.put(SERVERNAME, serverName); - values.put(JID, jid.toString()); - values.put(OPTIONS, subscription); - values.put(SYSTEMACCOUNT, systemAccount); - values.put(PHOTOURI, photoUri); - values.put(KEYS, keys.toString()); - values.put(AVATAR, avatar == null ? null : avatar.getFilename()); - values.put(LAST_PRESENCE, lastseen.presence); - values.put(LAST_TIME, lastseen.time); - values.put(GROUPS, groups.toString()); - return values; + synchronized (this.keys) { + final ContentValues values = new ContentValues(); + values.put(ACCOUNT, accountUuid); + values.put(SYSTEMNAME, systemName); + values.put(SERVERNAME, serverName); + values.put(JID, jid.toString()); + values.put(OPTIONS, subscription); + values.put(SYSTEMACCOUNT, systemAccount); + values.put(PHOTOURI, photoUri); + values.put(KEYS, keys.toString()); + values.put(AVATAR, avatar == null ? null : avatar.getFilename()); + values.put(LAST_PRESENCE, lastseen.presence); + values.put(LAST_TIME, lastseen.time); + values.put(GROUPS, groups.toString()); + return values; + } } public int getSubscription() { @@ -222,12 +239,8 @@ public class Contact implements ListItem, Blockable { return this.presences; } - public void setPresences(Presences pres) { - this.presences = pres; - } - - public void updatePresence(final String resource, final int status) { - this.presences.updatePresence(resource, status); + public void updatePresence(final String resource, final Presence presence) { + this.presences.updatePresence(resource, presence); } public void removePresence(final String resource) { @@ -239,8 +252,13 @@ public class Contact implements ListItem, Blockable { this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST); } - public int getMostAvailableStatus() { - return this.presences.getMostAvailableStatus(); + public Presence.Status getMostAvailableStatus() { + Presence p = this.presences.getMostAvailablePresence(); + if (p == null) { + return Presence.Status.OFFLINE; + } + + return p.getStatus(); } public boolean setPhotoUri(String uri) { @@ -287,60 +305,65 @@ public class Contact implements ListItem, Blockable { } public ArrayList<String> getOtrFingerprints() { - final ArrayList<String> fingerprints = new ArrayList<String>(); - try { - if (this.keys.has("otr_fingerprints")) { - final JSONArray prints = this.keys.getJSONArray("otr_fingerprints"); - for (int i = 0; i < prints.length(); ++i) { - final String print = prints.isNull(i) ? null : prints.getString(i); - if (print != null && !print.isEmpty()) { - fingerprints.add(prints.getString(i)); + synchronized (this.keys) { + final ArrayList<String> fingerprints = new ArrayList<String>(); + try { + if (this.keys.has("otr_fingerprints")) { + final JSONArray prints = this.keys.getJSONArray("otr_fingerprints"); + for (int i = 0; i < prints.length(); ++i) { + final String print = prints.isNull(i) ? null : prints.getString(i); + if (print != null && !print.isEmpty()) { + fingerprints.add(prints.getString(i)); + } } } - } - } catch (final JSONException ignored) { + } catch (final JSONException ignored) { + } + return fingerprints; } - return fingerprints; } - public boolean addOtrFingerprint(String print) { - if (getOtrFingerprints().contains(print)) { - return false; - } - try { - JSONArray fingerprints; - if (!this.keys.has("otr_fingerprints")) { - fingerprints = new JSONArray(); - - } else { - fingerprints = this.keys.getJSONArray("otr_fingerprints"); + synchronized (this.keys) { + if (getOtrFingerprints().contains(print)) { + return false; + } + 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); + return true; + } catch (final JSONException ignored) { + return false; } - fingerprints.put(print); - this.keys.put("otr_fingerprints", fingerprints); - return true; - } catch (final JSONException ignored) { - return false; } } public long getPgpKeyId() { - if (this.keys.has("pgp_keyid")) { - try { - return this.keys.getLong("pgp_keyid"); - } catch (JSONException e) { + synchronized (this.keys) { + if (this.keys.has("pgp_keyid")) { + try { + return this.keys.getLong("pgp_keyid"); + } catch (JSONException e) { + return 0; + } + } else { return 0; } - } else { - return 0; } } public void setPgpKeyId(long keyId) { - try { - this.keys.put("pgp_keyid", keyId); - } catch (final JSONException ignored) { - + synchronized (this.keys) { + try { + this.keys.put("pgp_keyid", keyId); + } catch (final JSONException ignored) { + } } } @@ -376,11 +399,13 @@ public class Contact implements ListItem, Blockable { this.resetOption(Options.TO); this.setOption(Options.FROM); this.resetOption(Options.PREEMPTIVE_GRANT); + this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST); break; case "both": this.setOption(Options.TO); this.setOption(Options.FROM); this.resetOption(Options.PREEMPTIVE_GRANT); + this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST); break; case "none": this.resetOption(Options.FROM); @@ -447,24 +472,26 @@ public class Contact implements ListItem, Blockable { } 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; + synchronized (this.keys) { + 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); } - this.keys.put("otr_fingerprints", newPrints); + return success; + } catch (JSONException e) { + return false; } - return success; - } catch (JSONException e) { - return false; } } @@ -504,6 +531,10 @@ public class Contact implements ListItem, Blockable { return account.getJid().toBareJid().equals(getJid().toBareJid()); } + public void setCommonName(String cn) { + this.commonName = cn; + } + public static class Lastseen { public long time; public String presence; diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java b/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java index e26f7944..0b70d938 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java @@ -19,8 +19,9 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; +import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.Config; -import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; import de.thedevstack.conversationsplus.xmpp.chatstate.ChatState; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; @@ -47,7 +48,7 @@ public class Conversation extends AbstractEntity implements Blockable { 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"; - public static final String ATTRIBUTE_LAST_MESSAGE_TRANSMITTED = "last_message_transmitted"; + public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify"; private String name; private String contactUuid; @@ -81,6 +82,8 @@ public class Conversation extends AbstractEntity implements Blockable { private ChatState mOutgoingChatState = Config.DEFAULT_CHATSTATE; private ChatState mIncomingChatState = Config.DEFAULT_CHATSTATE; private String mLastReceivedOtrMessageId = null; + private String mFirstMamReference = null; + private Message correctingMessage; public boolean hasMessagesLeftOnServer() { return messagesLeftOnServer; @@ -112,6 +115,16 @@ public class Conversation extends AbstractEntity implements Blockable { } } + public void findUnreadMessages(OnMessageFound onMessageFound) { + synchronized (this.messages) { + for(Message message : this.messages) { + if (!message.isRead()) { + onMessageFound.onMessageFound(message); + } + } + } + } + public void findMessagesWithFiles(final OnMessageFound onMessageFound) { synchronized (this.messages) { for (final Message message : this.messages) { @@ -180,13 +193,13 @@ public class Conversation extends AbstractEntity implements Blockable { } } - public void findUnsentMessagesWithOtrEncryption(OnMessageFound onMessageFound) { + public void findUnsentMessagesWithEncryption(int encryptionType, OnMessageFound onMessageFound) { synchronized (this.messages) { for (Message message : this.messages) { if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING) - && (message.getEncryption() == Message.ENCRYPTION_OTR)) { + && (message.getEncryption() == encryptionType)) { onMessageFound.onMessageFound(message); - } + } } } } @@ -202,14 +215,43 @@ public class Conversation extends AbstractEntity implements Blockable { } } - public Message findSentMessageWithUuid(String uuid) { + public Message findSentMessageWithUuidOrRemoteId(String id) { + synchronized (this.messages) { + for (Message message : this.messages) { + if (id.equals(message.getUuid()) + || (message.getStatus() >= Message.STATUS_SEND + && id.equals(message.getRemoteMsgId()))) { + return message; + } + } + } + return null; + } + + public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart, boolean received, boolean carbon) { + synchronized (this.messages) { + for(int i = this.messages.size() - 1; i >= 0; --i) { + Message message = messages.get(i); + if (counterpart.equals(message.getCounterpart()) + && ((message.getStatus() == Message.STATUS_RECEIVED) == received) + && (carbon == message.isCarbon() || received) ) { + if (id.equals(message.getRemoteMsgId())) { + return message; + } else { + return null; + } + } + } + } + return null; + } + + public Message findSentMessageWithUuid(String id) { synchronized (this.messages) { for (Message message : this.messages) { - if (uuid.equals(message.getUuid()) - || (message.getStatus() >= Message.STATUS_SEND && uuid - .equals(message.getRemoteMsgId()))) { + if (id.equals(message.getUuid())) { return message; - } + } } } return null; @@ -256,9 +298,24 @@ public class Conversation extends AbstractEntity implements Blockable { } } + public void setFirstMamReference(String reference) { + this.mFirstMamReference = reference; + } + + public String getFirstMamReference() { + return this.mFirstMamReference; + } + + public void setCorrectingMessage(Message correctingMessage) { + this.correctingMessage = correctingMessage; + } + + public Message getCorrectingMessage() { + return this.correctingMessage; + } public interface OnMessageFound { - public void onMessageFound(final Message message); + void onMessageFound(final Message message); } public Conversation(final String name, final Account account, final Jid contactJid, @@ -291,13 +348,17 @@ public class Conversation extends AbstractEntity implements Blockable { return (this.messages.size() == 0) || this.messages.get(this.messages.size() - 1).isRead(); } - public void markRead() { - for (int i = this.messages.size() - 1; i >= 0; --i) { - if (messages.get(i).isRead()) { - break; + public List<Message> markRead() { + final List<Message> unread = new ArrayList<>(); + synchronized (this.messages) { + for(Message message : this.messages) { + if (!message.isRead()) { + message.markRead(); + unread.add(message); + } } - this.messages.get(i).markRead(); } + return unread; } public Message getLatestMarkableMessage() { @@ -330,8 +391,8 @@ public class Conversation extends AbstractEntity implements Blockable { if (getMode() == MODE_MULTI) { if (getMucOptions().getSubject() != null) { return getMucOptions().getSubject(); - } else if (bookmark != null && bookmark.getName() != null) { - return bookmark.getName(); + } else if (bookmark != null && bookmark.getBookmarkName() != null) { + return bookmark.getBookmarkName(); } else { String generatedName = getMucOptions().createNameFromParticipants(); if (generatedName != null) { @@ -456,15 +517,18 @@ public class Conversation extends AbstractEntity implements Blockable { return mSmp; } - public void startOtrIfNeeded() { - if (this.otrSession != null - && this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) { + public boolean startOtrIfNeeded() { + if (this.otrSession != null && this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) { try { this.otrSession.startSession(); + return true; } catch (OtrException e) { this.resetOtrSession(); + return false; } - } + } else { + return true; + } } public boolean endOtrIfNeeded() { @@ -520,6 +584,13 @@ public class Conversation extends AbstractEntity implements Blockable { return getContact().getOtrFingerprints().contains(getOtrFingerprint()); } + /** + * short for is Private and Non-anonymous + */ + private boolean isPnNA() { + return mode == MODE_SINGLE || (getMucOptions().membersOnly() && getMucOptions().nonanonymous()); + } + public synchronized MucOptions getMucOptions() { if (this.mucOptions == null) { this.mucOptions = new MucOptions(this); @@ -543,42 +614,70 @@ public class Conversation extends AbstractEntity implements Blockable { return this.nextCounterpart; } - 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; + private int getMostRecentlyUsedOutgoingEncryption() { + synchronized (this.messages) { + for(int i = this.messages.size() -1; i >= 0; --i) { + final Message m = this.messages.get(i); + if (!m.isCarbon() && m.getStatus() != Message.STATUS_RECEIVED) { + final int e = m.getEncryption(); + if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) { + return Message.ENCRYPTION_PGP; + } else { + return e; + } + } + } } + return Message.ENCRYPTION_NONE; } - 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; + private int getMostRecentlyUsedIncomingEncryption() { + synchronized (this.messages) { + for(int i = this.messages.size() -1; i >= 0; --i) { + final Message m = this.messages.get(i); + if (m.getStatus() == Message.STATUS_RECEIVED) { + final int e = m.getEncryption(); + if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) { + return Message.ENCRYPTION_PGP; } else { - return latest; + return e; } + } + } + } + return Message.ENCRYPTION_NONE; + } + + public int getNextEncryption() { + final AxolotlService axolotlService = getAccount().getAxolotlService(); + int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1); + if (next == -1) { + if (Config.X509_VERIFICATION && mode == MODE_SINGLE) { + if (axolotlService != null && axolotlService.isContactAxolotlCapable(getContact())) { + return Message.ENCRYPTION_AXOLOTL; } else { - return latest; + return Message.ENCRYPTION_NONE; } + } + int outgoing = this.getMostRecentlyUsedOutgoingEncryption(); + if (outgoing == Message.ENCRYPTION_NONE) { + next = this.getMostRecentlyUsedIncomingEncryption(); } else { - return latest; + next = outgoing; } } - if (next == Message.ENCRYPTION_NONE && force - && getMode() == MODE_SINGLE) { - return Message.ENCRYPTION_OTR; - } else { - return next; + if (!Config.supportUnencrypted() + && (mode == MODE_SINGLE || Config.supportOpenPgpOnly()) + && next <= 0) { + if (Config.supportOmemo() && (axolotlService != null && axolotlService.isContactAxolotlCapable(getContact()) || !Config.multipleEncryptionChoices())) { + return Message.ENCRYPTION_AXOLOTL; + } else if (Config.supportOtr()) { + return Message.ENCRYPTION_OTR; + } else if (Config.supportOpenPgp()) { + return Message.ENCRYPTION_PGP; + } } + return next; } public void setNextEncryption(int encryption) { @@ -639,37 +738,32 @@ public class Conversation extends AbstractEntity implements Blockable { synchronized (this.messages) { for (int i = this.messages.size() - 1; i >= 0; --i) { Message message = this.messages.get(i); - if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) && message.getBody() != null && message.getBody().equals(body)) { - return message; + if (message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) { + String otherBody; + if (message.hasFileOnRemoteHost()) { + otherBody = message.getFileParams().url.toString(); + } else { + otherBody = message.body; + } + if (otherBody != null && otherBody.equals(body)) { + return message; + } } } return null; } } - public boolean setLastMessageTransmitted(long value) { - long before = getLastMessageTransmitted(); - if (value - before > 1000) { - this.setAttribute(ATTRIBUTE_LAST_MESSAGE_TRANSMITTED, String.valueOf(value)); - return true; - } else { - return false; - } - } - public long getLastMessageTransmitted() { - long timestamp = getLongAttribute(ATTRIBUTE_LAST_MESSAGE_TRANSMITTED,0); - if (timestamp == 0) { - synchronized (this.messages) { - for(int i = this.messages.size() - 1; i >= 0; --i) { - Message message = this.messages.get(i); - if (message.getStatus() == Message.STATUS_RECEIVED) { - return message.getTimeSent(); - } + synchronized (this.messages) { + for(int i = this.messages.size() - 1; i >= 0; --i) { + Message message = this.messages.get(i); + if (message.getStatus() == Message.STATUS_RECEIVED || message.isCarbon()) { + return message.getTimeSent(); } } } - return timestamp; + return 0; } public void setMutedTill(long value) { @@ -680,6 +774,10 @@ public class Conversation extends AbstractEntity implements Blockable { return System.currentTimeMillis() < this.getLongAttribute(ATTRIBUTE_MUTED_TILL, 0); } + public boolean alwaysNotify() { + return mode == MODE_SINGLE || getBooleanAttribute(ATTRIBUTE_ALWAYS_NOTIFY, Config.ALWAYS_NOTIFY_BY_DEFAULT || isPnNA()); + } + public boolean setAttribute(String key, String value) { try { this.attributes.put(key, value); @@ -723,6 +821,15 @@ public class Conversation extends AbstractEntity implements Blockable { } } + public boolean getBooleanAttribute(String key, boolean defaultValue) { + String value = this.getAttribute(key); + if (value == null) { + return defaultValue; + } else { + return Boolean.parseBoolean(value); + } + } + public void add(Message message) { message.setConversation(this); synchronized (this.messages) { @@ -730,10 +837,18 @@ public class Conversation extends AbstractEntity implements Blockable { } } + public void prepend(Message message) { + message.setConversation(this); + synchronized (this.messages) { + this.messages.add(0,message); + } + } + public void addAll(int index, List<Message> messages) { synchronized (this.messages) { this.messages.addAll(index, messages); } + account.getPgpDecryptionService().addAll(messages); } public void sort() { @@ -757,6 +872,9 @@ public class Conversation extends AbstractEntity implements Blockable { } public int unreadCount() { + if (getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0) == Long.MAX_VALUE) { + return 0; + } synchronized (this.messages) { int count = 0; for(int i = this.messages.size() - 1; i >= 0; --i) { @@ -764,10 +882,7 @@ public class Conversation extends AbstractEntity implements Blockable { if (message.isRead()) { return count; } - if (getMode() == Conversation.MODE_SINGLE - || ConversationsPlusPreferences.alwaysNotifyInConference() - || account.getXmppConnectionService().getNotificationService().wasHighlightedOrPrivate(message) - ) { + if (alwaysNotify() || MessageUtil.wasHighlightedOrPrivate(message)) { ++count; } } diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/DownloadableFile.java b/src/main/java/de/thedevstack/conversationsplus/entities/DownloadableFile.java index 4031e546..424d0301 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/DownloadableFile.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/DownloadableFile.java @@ -1,26 +1,7 @@ package de.thedevstack.conversationsplus.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.net.URLConnection; -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 de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; + import de.thedevstack.conversationsplus.utils.MimeUtils; public class DownloadableFile extends File { @@ -29,8 +10,7 @@ public class DownloadableFile extends File { private long expectedSize = 0; private String sha1sum; - private Key aeskey; - private String mime; + private byte[] aeskey; private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf }; @@ -44,15 +24,7 @@ public class DownloadableFile extends File { } public long getExpectedSize() { - if (this.aeskey != null) { - if (this.expectedSize == 0) { - return 0; - } else { - return (this.expectedSize / 16 + 1) * 16; - } - } else { - return this.expectedSize; - } + return this.expectedSize; } public String getMimeType() { @@ -78,91 +50,38 @@ public class DownloadableFile extends File { this.sha1sum = sum; } - public void setKey(byte[] key) { - if (key.length == 48) { + public void setKeyAndIv(byte[] keyIvCombo) { + if (keyIvCombo.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"); + System.arraycopy(keyIvCombo, 0, iv, 0, 16); + System.arraycopy(keyIvCombo, 16, secretKey, 0, 32); + this.aeskey = secretKey; this.iv = iv; - } else if (key.length >= 32) { + } else if (keyIvCombo.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) { + System.arraycopy(keyIvCombo, 0, secretKey, 0, 32); + this.aeskey = secretKey; + } else if (keyIvCombo.length >= 16) { byte[] secretKey = new byte[16]; - System.arraycopy(key, 0, secretKey, 0, 16); - this.aeskey = new SecretKeySpec(secretKey, "AES"); + System.arraycopy(keyIvCombo, 0, secretKey, 0, 16); + this.aeskey = secretKey; } } - public Key getKey() { - return this.aeskey; + public void setKey(byte[] key) { + this.aeskey = key; } - 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); - Logging.d(Config.LOGTAG, "opening encrypted input stream"); - return new CipherInputStream(new FileInputStream(this), cipher); - } catch (NoSuchAlgorithmException e) { - Logging.d(Config.LOGTAG, "no such algo: " + e.getMessage()); - return null; - } catch (NoSuchPaddingException e) { - Logging.d(Config.LOGTAG, "no such padding: " + e.getMessage()); - return null; - } catch (InvalidKeyException e) { - Logging.d(Config.LOGTAG, "invalid key: " + e.getMessage()); - return null; - } catch (InvalidAlgorithmParameterException e) { - Logging.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); - return null; - } catch (FileNotFoundException e) { - return null; - } - } + public void setIv(byte[] iv) { + this.iv = iv; } - 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); - Logging.d(Config.LOGTAG, "opening encrypted output stream"); - return new CipherOutputStream(new FileOutputStream(this), - cipher); - } catch (NoSuchAlgorithmException e) { - Logging.d(Config.LOGTAG, "no such algo: " + e.getMessage()); - return null; - } catch (NoSuchPaddingException e) { - Logging.d(Config.LOGTAG, "no such padding: " + e.getMessage()); - return null; - } catch (InvalidKeyException e) { - Logging.d(Config.LOGTAG, "invalid key: " + e.getMessage()); - return null; - } catch (InvalidAlgorithmParameterException e) { - Logging.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); - return null; - } catch (FileNotFoundException e) { - return null; - } - } + public byte[] getKey() { + return this.aeskey; + } + + public byte[] getIv() { + return this.iv; } } diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/ListItem.java b/src/main/java/de/thedevstack/conversationsplus/entities/ListItem.java index 9daf90d1..24dc7e94 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/ListItem.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/ListItem.java @@ -5,15 +5,17 @@ import java.util.List; import de.thedevstack.conversationsplus.xmpp.jid.Jid; public interface ListItem extends Comparable<ListItem> { - public String getDisplayName(); + String getDisplayName(); - public Jid getJid(); + String getDisplayJid(); - public List<Tag> getTags(); + Jid getJid(); public int getStatusColor(); - public final class Tag { + List<Tag> getTags(); + + final class Tag { private final String name; private final int color; @@ -31,5 +33,5 @@ public interface ListItem extends Comparable<ListItem> { } } - public boolean match(final String needle); + boolean match(final String needle); } diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Message.java b/src/main/java/de/thedevstack/conversationsplus/entities/Message.java index a5d06f46..d03cab92 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Message.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Message.java @@ -8,9 +8,9 @@ import java.net.URL; import java.util.Arrays; import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; import de.thedevstack.conversationsplus.utils.GeoHelper; import de.thedevstack.conversationsplus.utils.MimeUtils; -import de.thedevstack.conversationsplus.utils.UIHelper; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; @@ -34,6 +34,7 @@ public class Message extends AbstractEntity { 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 ENCRYPTION_AXOLOTL = 5; public static final int TYPE_TEXT = 0; public static final int TYPE_IMAGE = 1; @@ -49,9 +50,13 @@ public class Message extends AbstractEntity { public static final String ENCRYPTION = "encryption"; public static final String STATUS = "status"; public static final String TYPE = "type"; + public static final String CARBON = "carbon"; + public static final String EDITED = "edited"; public static final String REMOTE_MSG_ID = "remoteMsgId"; public static final String SERVER_MSG_ID = "serverMsgId"; public static final String RELATIVE_FILE_PATH = "relativeFilePath"; + public static final String FINGERPRINT = "axolotl_fingerprint"; + public static final String READ = "read"; public static final String ME_COMMAND = "/me "; @@ -59,12 +64,14 @@ public class Message extends AbstractEntity { protected String conversationUuid; protected Jid counterpart; protected Jid trueCounterpart; - private String body; + protected String body; protected String encryptedBody; protected long timeSent; protected int encryption; protected int status; protected int type; + protected boolean carbon = false; + protected String edited = null; protected String relativeFilePath; protected boolean read = true; protected String remoteMsgId = null; @@ -73,6 +80,7 @@ public class Message extends AbstractEntity { protected Transferable transferable = null; private Message mNextMessage = null; private Message mPreviousMessage = null; + private String axolotlFingerprint = null; private Message() { @@ -92,16 +100,22 @@ public class Message extends AbstractEntity { encryption, status, TYPE_TEXT, + false, null, null, + null, + null, + true, null); this.conversation = conversation; } private Message(final String uuid, final String conversationUUid, final Jid counterpart, final Jid trueCounterpart, final String body, final long timeSent, - final int encryption, final int status, final int type, final String remoteMsgId, - final String relativeFilePath, final String serverMsgId) { + final int encryption, final int status, final int type, final boolean carbon, + final String remoteMsgId, final String relativeFilePath, + final String serverMsgId, final String fingerprint, final boolean read, + final String edited) { this.uuid = uuid; this.conversationUuid = conversationUUid; this.counterpart = counterpart; @@ -111,9 +125,13 @@ public class Message extends AbstractEntity { this.encryption = encryption; this.status = status; this.type = type; + this.carbon = carbon; this.remoteMsgId = remoteMsgId; this.relativeFilePath = relativeFilePath; this.serverMsgId = serverMsgId; + this.axolotlFingerprint = fingerprint; + this.read = read; + this.edited = edited; } public static Message fromCursor(Cursor cursor) { @@ -148,13 +166,17 @@ public class Message extends AbstractEntity { cursor.getInt(cursor.getColumnIndex(ENCRYPTION)), cursor.getInt(cursor.getColumnIndex(STATUS)), cursor.getInt(cursor.getColumnIndex(TYPE)), + cursor.getInt(cursor.getColumnIndex(CARBON)) > 0, cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)), cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)), - cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID))); + cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)), + cursor.getString(cursor.getColumnIndex(FINGERPRINT)), + cursor.getInt(cursor.getColumnIndex(READ)) > 0, + cursor.getString(cursor.getColumnIndex(EDITED))); } public static Message createStatusMessage(Conversation conversation, String body) { - Message message = new Message(); + final Message message = new Message(); message.setType(Message.TYPE_STATUS); message.setConversation(conversation); message.setBody(body); @@ -181,9 +203,13 @@ public class Message extends AbstractEntity { values.put(ENCRYPTION, encryption); values.put(STATUS, status); values.put(TYPE, type); + values.put(CARBON, carbon ? 1 : 0); values.put(REMOTE_MSG_ID, remoteMsgId); values.put(RELATIVE_FILE_PATH, relativeFilePath); values.put(SERVER_MSG_ID, serverMsgId); + values.put(FINGERPRINT, axolotlFingerprint); + values.put(READ,read ? 1 : 0); + values.put(EDITED, edited); return values; } @@ -304,10 +330,30 @@ public class Message extends AbstractEntity { this.type = type; } + public boolean isCarbon() { + return carbon; + } + + public void setCarbon(boolean carbon) { + this.carbon = carbon; + } + + public void setEdited(String edited) { + this.edited = edited; + } + + public boolean edited() { + return this.edited != null; + } + public void setTrueCounterpart(Jid trueCounterpart) { this.trueCounterpart = trueCounterpart; } + public Jid getTrueCounterpart() { + return this.trueCounterpart; + } + public Transferable getTransferable() { return this.transferable; } @@ -333,7 +379,9 @@ public class Message extends AbstractEntity { if (message.getRemoteMsgId() != null) { return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid)) && this.counterpart.equals(message.getCounterpart()) - && body.equals(otherBody); + && (body.equals(otherBody) + ||(message.getEncryption() == Message.ENCRYPTION_PGP + && message.getRemoteMsgId().matches("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"))) ; } else { return this.remoteMsgId == null && this.counterpart.equals(message.getCounterpart()) @@ -371,25 +419,43 @@ public class Message extends AbstractEntity { } } + public boolean isLastCorrectableMessage() { + Message next = next(); + while(next != null) { + if (next.isCorrectable()) { + return false; + } + next = next.next(); + } + return isCorrectable(); + } + + private boolean isCorrectable() { + return getStatus() != STATUS_RECEIVED && !isCarbon(); + } + public boolean mergeable(final Message message) { return message != null && (message.getType() == Message.TYPE_TEXT && this.getTransferable() == null && message.getTransferable() == null && message.getEncryption() != Message.ENCRYPTION_PGP && + message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED && this.getType() == message.getType() && //this.getStatus() == message.getStatus() && isStatusMergeable(this.getStatus(), message.getStatus()) && this.getEncryption() == message.getEncryption() && this.getCounterpart() != null && this.getCounterpart().equals(message.getCounterpart()) && + this.edited() == message.edited() && (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && !GeoHelper.isGeoUri(message.getBody()) && !GeoHelper.isGeoUri(this.getBody()) && message.treatAsDownloadable() == Decision.NEVER && this.treatAsDownloadable() == Decision.NEVER && !message.getBody().startsWith(ME_COMMAND) && - !this.getBody().startsWith(ME_COMMAND) + !this.getBody().startsWith(ME_COMMAND) && + this.isTrusted() == message.isTrusted() ); } @@ -405,11 +471,14 @@ public class Message extends AbstractEntity { } public String getMergedBody() { - final Message next = this.next(); - if (this.mergeable(next)) { - return getBody() + MERGE_SEPARATOR + next.getMergedBody(); + StringBuilder body = new StringBuilder(this.body); + Message current = this; + while(current.mergeable(current.next())) { + current = current.next(); + body.append(MERGE_SEPARATOR); + body.append(current.getBody()); } - return getBody(); + return body.toString(); } public boolean hasMeCommand() { @@ -417,20 +486,23 @@ public class Message extends AbstractEntity { } public int getMergedStatus() { - final Message next = this.next(); - if (this.mergeable(next)) { - return next.getStatus(); + int status = this.status; + Message current = this; + while(current.mergeable(current.next())) { + current = current.next(); + status = current.status; } - return getStatus(); + return status; } public long getMergedTimeSent() { - Message next = this.next(); - if (this.mergeable(next)) { - return next.getMergedTimeSent(); - } else { - return getTimeSent(); + long time = this.timeSent; + Message current = this; + while(current.mergeable(current.next())) { + current = current.next(); + time = current.timeSent; } + return time; } public boolean wasMergedIntoPrevious() { @@ -463,6 +535,14 @@ public class Message extends AbstractEntity { } } + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getEditedId() { + return edited; + } + public enum Decision { MUST, SHOULD, @@ -478,18 +558,15 @@ public class Message extends AbstractEntity { if (path == null || path.isEmpty()) { return null; } - + String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase(); - int dotPosition = filename.lastIndexOf("."); - if (dotPosition != -1) - { + if (dotPosition != -1) { String extension = filename.substring(dotPosition + 1); - // we want the real file extension, not the crypto one if (Arrays.asList(Transferable.VALID_CRYPTO_EXTENSIONS).contains(extension)) { - return extractRelevantExtension(path.substring(0,dotPosition)); + return extractRelevantExtension(filename.substring(0,dotPosition)); } else { return extension; } @@ -673,4 +750,55 @@ public class Message extends AbstractEntity { public int width = 0; public int height = 0; } + + public void setAxolotlFingerprint(String fingerprint) { + this.axolotlFingerprint = fingerprint; + } + + public String getAxolotlFingerprint() { + return axolotlFingerprint; + } + + public boolean isTrusted() { + XmppAxolotlSession.Trust t = conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint); + return t != null && t.trusted(); + } + + private int getPreviousEncryption() { + for (Message iterator = this.prev(); iterator != null; iterator = iterator.prev()){ + if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) { + continue; + } + return iterator.getEncryption(); + } + return ENCRYPTION_NONE; + } + + private int getNextEncryption() { + for (Message iterator = this.next(); iterator != null; iterator = iterator.next()){ + if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) { + continue; + } + return iterator.getEncryption(); + } + return conversation.getNextEncryption(); + } + + public boolean isValidInSession() { + int pastEncryption = getCleanedEncryption(this.getPreviousEncryption()); + int futureEncryption = getCleanedEncryption(this.getNextEncryption()); + + boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE + || futureEncryption == ENCRYPTION_NONE + || pastEncryption != futureEncryption; + + return inUnencryptedSession || getCleanedEncryption(this.getEncryption()) == pastEncryption; + } + + private static int getCleanedEncryption(int encryption) { + if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) { + return ENCRYPTION_PGP; + } + return encryption; + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/MucOptions.java b/src/main/java/de/thedevstack/conversationsplus/entities/MucOptions.java index 22155f3e..f0eb83de 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/MucOptions.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/MucOptions.java @@ -1,21 +1,31 @@ package de.thedevstack.conversationsplus.entities; +import android.annotation.SuppressLint; + import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.Map; import de.thedevstack.conversationsplus.R; -import de.thedevstack.conversationsplus.crypto.PgpEngine; -import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.forms.Data; +import de.thedevstack.conversationsplus.xmpp.forms.Field; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; -import de.thedevstack.conversationsplus.xmpp.stanzas.PresencePacket; - -import android.annotation.SuppressLint; +import de.thedevstack.conversationsplus.xmpp.pep.Avatar; @SuppressLint("DefaultLocale") public class MucOptions { + public Account getAccount() { + return this.conversation.getAccount(); + } + + public void setSelf(User user) { + this.self = user; + } + public enum Affiliation { OWNER("owner", 4, R.string.owner), ADMIN("admin", 3, R.string.admin), @@ -23,7 +33,7 @@ public class MucOptions { OUTCAST("outcast", 0, R.string.outcast), NONE("none", 1, R.string.no_affiliation); - private Affiliation(String string, int rank, int resId) { + Affiliation(String string, int rank, int resId) { this.string = string; this.resId = resId; this.rank = rank; @@ -52,18 +62,20 @@ public class MucOptions { } public enum Role { - MODERATOR("moderator", R.string.moderator), - VISITOR("visitor", R.string.visitor), - PARTICIPANT("participant", R.string.participant), - NONE("none", R.string.no_role); + MODERATOR("moderator", R.string.moderator,3), + VISITOR("visitor", R.string.visitor,1), + PARTICIPANT("participant", R.string.participant,2), + NONE("none", R.string.no_role,0); - private Role(String string, int resId) { + Role(String string, int resId, int rank) { this.string = string; this.resId = resId; + this.rank = rank; } private String string; private int resId; + private int rank; public int getResId() { return resId; @@ -73,51 +85,59 @@ public class MucOptions { public String toString() { return this.string; } - } - public static final int ERROR_NO_ERROR = 0; - public static final int ERROR_NICK_IN_USE = 1; - public static final int ERROR_UNKNOWN = 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 boolean ranks(Role role) { + return rank >= role.rank; + } + } - public static final int KICKED_FROM_ROOM = 9; + public enum Error { + NO_RESPONSE, + NONE, + NICK_IN_USE, + PASSWORD_REQUIRED, + BANNED, + MEMBERS_ONLY, + KICKED, + SHUTDOWN, + UNKNOWN + } public static final String STATUS_CODE_ROOM_CONFIG_CHANGED = "104"; public static final String STATUS_CODE_SELF_PRESENCE = "110"; public static final String STATUS_CODE_BANNED = "301"; public static final String STATUS_CODE_CHANGED_NICK = "303"; public static final String STATUS_CODE_KICKED = "307"; - public static final String STATUS_CODE_LOST_MEMBERSHIP = "321"; + public static final String STATUS_CODE_AFFILIATION_CHANGE = "321"; + public static final String STATUS_CODE_LOST_MEMBERSHIP = "322"; + public static final String STATUS_CODE_SHUTDOWN = "332"; private interface OnEventListener { - public void onSuccess(); + void onSuccess(); - public void onFailure(); + void onFailure(); } public interface OnRenameListener extends OnEventListener { } - public interface OnJoinListener extends OnEventListener { - - } - - public class User { + public static class User { private Role role = Role.NONE; private Affiliation affiliation = Affiliation.NONE; - private String name; private Jid jid; + private Jid fullJid; private long pgpKeyId = 0; + private Avatar avatar; + private MucOptions options; - public String getName() { - return name; + public User(MucOptions options, Jid from) { + this.options = options; + this.fullJid = from; } - public void setName(String user) { - this.name = user; + public String getName() { + return this.fullJid.getResourcepart(); } public void setJid(Jid jid) { @@ -158,7 +178,7 @@ public class MucOptions { return false; } else { User o = (User) other; - return name != null && name.equals(o.name) + return getName() != null && getName().equals(o.getName()) && jid != null && jid.equals(o.jid) && affiliation == o.affiliation && role == o.role; @@ -198,26 +218,48 @@ public class MucOptions { } public Contact getContact() { - return account.getRoster().getContactFromRoster(getJid()); + return getAccount().getRoster().getContactFromRoster(getJid()); + } + + public boolean setAvatar(Avatar avatar) { + if (this.avatar != null && this.avatar.equals(avatar)) { + return false; + } else { + this.avatar = avatar; + return true; + } + } + + public String getAvatar() { + return avatar == null ? null : avatar.getFilename(); + } + + public Account getAccount() { + return options.getAccount(); + } + + public Jid getFullJid() { + return fullJid; } } private Account account; - private List<User> users = new CopyOnWriteArrayList<>(); + private final Map<String, User> users = Collections.synchronizedMap(new LinkedHashMap<String, User>()); private List<String> features = new ArrayList<>(); + private Data form = new Data(); private Conversation conversation; private boolean isOnline = false; - private int error = ERROR_UNKNOWN; - private OnRenameListener onRenameListener = null; - private OnJoinListener onJoinListener = null; - private User self = new User(); + private Error error = Error.NONE; + public OnRenameListener onRenameListener = null; + private User self; private String subject = null; private String password = null; - private boolean mNickChangingInProgress = false; + public boolean mNickChangingInProgress = false; public MucOptions(Conversation conversation) { this.account = conversation.getAccount(); this.conversation = conversation; + this.self = new User(this,createJoinJid(getProposedNick())); } public void updateFeatures(ArrayList<String> features) { @@ -225,18 +267,39 @@ public class MucOptions { this.features.addAll(features); } + public void updateFormData(Data form) { + this.form = form; + } + public boolean hasFeature(String feature) { return this.features.contains(feature); } public boolean canInvite() { - return !membersOnly() || self.getAffiliation().ranks(Affiliation.ADMIN); + Field field = this.form.getFieldByName("muc#roomconfig_allowinvites"); + return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue())); + } + + public boolean canChangeSubject() { + Field field = this.form.getFieldByName("muc#roomconfig_changesubject"); + return self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue())); + } + + public boolean participating() { + return !online() + || self.getRole().ranks(Role.PARTICIPANT) + || hasFeature("muc_unmoderated"); } public boolean membersOnly() { return hasFeature("muc_membersonly"); } + public boolean mamSupport() { + // Update with "urn:xmpp:mam:1" once we support it + return hasFeature("urn:xmpp:mam:0"); + } + public boolean nonanonymous() { return hasFeature("muc_nonanonymous"); } @@ -245,135 +308,55 @@ public class MucOptions { return hasFeature("muc_persistent"); } - 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 boolean moderated() { + return hasFeature("muc_moderated"); + } + + public User deleteUser(String name) { + return this.users.remove(name); } 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) { - final Jid from = packet.getFrom(); - if (!from.isBareJid()) { - final String name = from.getResourcepart(); - final String type = packet.getAttribute("type"); - final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user"); - final List<String> codes = getStatusCodes(x); - if (type == null) { - User user = new User(); - if (x != null) { - Element item = x.findChild("item"); - if (item != null && name != null) { - user.setName(name); - user.setAffiliation(item.getAttribute("affiliation")); - user.setRole(item.getAttribute("role")); - user.setJid(item.getAttributeAsJid("jid")); - if (codes.contains(STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(this.conversation.getJid())) { - this.isOnline = true; - this.error = ERROR_NO_ERROR; - self = user; - if (mNickChangingInProgress) { - onRenameListener.onSuccess(); - mNickChangingInProgress = false; - } else if (this.onJoinListener != null) { - this.onJoinListener.onSuccess(); - this.onJoinListener = null; - } - } else { - addUser(user); - } - if (pgp != null) { - Element signed = packet.findChild("x", "jabber:x:signed"); - if (signed != null) { - Element status = packet.findChild("status"); - String msg; - if (status != null) { - msg = status.getContent(); - } else { - msg = ""; - } - user.setPgpKeyId(pgp.fetchKeyId(account, msg, - signed.getContent())); - } - } - } - } - } else if (type.equals("unavailable")) { - if (codes.contains(STATUS_CODE_SELF_PRESENCE) || - packet.getFrom().equals(this.conversation.getJid())) { - if (codes.contains(STATUS_CODE_CHANGED_NICK)) { - this.mNickChangingInProgress = true; - } else if (codes.contains(STATUS_CODE_KICKED)) { - setError(KICKED_FROM_ROOM); - } else if (codes.contains(STATUS_CODE_BANNED)) { - setError(ERROR_BANNED); - } else if (codes.contains(STATUS_CODE_LOST_MEMBERSHIP)) { - setError(ERROR_MEMBERS_ONLY); - } else { - setError(ERROR_UNKNOWN); - } - } else { - deleteUser(name); - } - } else if (type.equals("error")) { - Element error = packet.findChild("error"); - if (error != null && error.hasChild("conflict")) { - if (isOnline) { - if (onRenameListener != null) { - onRenameListener.onFailure(); - } - } else { - setError(ERROR_NICK_IN_USE); - } - } else if (error != null && error.hasChild("not-authorized")) { - setError(ERROR_PASSWORD_REQUIRED); - } else if (error != null && error.hasChild("forbidden")) { - setError(ERROR_BANNED); - } else if (error != null && error.hasChild("registration-required")) { - setError(ERROR_MEMBERS_ONLY); - } - } - } + this.users.put(user.getName(), user); } - private void setError(int error) { - this.isOnline = false; + public User findUser(String name) { + return this.users.get(name); + } + + public boolean isUserInRoom(String name) { + return findUser(name) != null; + } + + public void setError(Error error) { + this.isOnline = isOnline && error == Error.NONE; this.error = error; - if (onJoinListener != null) { - onJoinListener.onFailure(); - onJoinListener = null; - } } - private List<String> getStatusCodes(Element x) { - List<String> codes = new ArrayList<>(); - if (x != null) { - for (Element child : x.getChildren()) { - if (child.getName().equals("status")) { - String code = child.getAttribute("code"); - if (code != null) { - codes.add(code); - } - } + public void setOnline() { + this.isOnline = true; + } + + public ArrayList<User> getUsers() { + return new ArrayList<>(users.values()); + } + + public List<User> getUsers(int max) { + ArrayList<User> users = new ArrayList<>(); + int i = 1; + for(User user : this.users.values()) { + users.add(user); + if (i >= max) { + break; + } else { + ++i; } } - return codes; + return users; } - public List<User> getUsers() { - return this.users; + public int getUserCount() { + return this.users.size(); } public String getProposedNick() { @@ -400,7 +383,7 @@ public class MucOptions { return this.isOnline; } - public int getError() { + public Error getError() { return this.error; } @@ -408,13 +391,9 @@ public class MucOptions { this.onRenameListener = listener; } - public void setOnJoinListener(OnJoinListener listener) { - this.onJoinListener = listener; - } - public void setOffline() { this.users.clear(); - this.error = 0; + this.error = Error.NO_RESPONSE; this.isOnline = false; } @@ -432,8 +411,8 @@ public class MucOptions { public String createNameFromParticipants() { if (users.size() >= 2) { - List<String> names = new ArrayList<String>(); - for (User user : users) { + List<String> names = new ArrayList<>(); + for (User user : getUsers(5)) { Contact contact = user.getContact(); if (contact != null && !contact.getDisplayName().isEmpty()) { names.add(contact.getDisplayName().split("\\s+")[0]); @@ -456,20 +435,21 @@ public class MucOptions { public long[] getPgpKeyIds() { List<Long> ids = new ArrayList<>(); - for (User user : getUsers()) { + for (User user : this.users.values()) { if (user.getPgpKeyId() != 0) { ids.add(user.getPgpKeyId()); } } - long[] primitivLongArray = new long[ids.size()]; + ids.add(account.getPgpId()); + long[] primitiveLongArray = new long[ids.size()]; for (int i = 0; i < ids.size(); ++i) { - primitivLongArray[i] = ids.get(i); + primitiveLongArray[i] = ids.get(i); } - return primitivLongArray; + return primitiveLongArray; } public boolean pgpKeysInUse() { - for (User user : getUsers()) { + for (User user : this.users.values()) { if (user.getPgpKeyId() != 0) { return true; } @@ -478,7 +458,7 @@ public class MucOptions { } public boolean everybodyHasKeys() { - for (User user : getUsers()) { + for (User user : this.users.values()) { if (user.getPgpKeyId() == 0) { return false; } @@ -494,13 +474,9 @@ public class MucOptions { } } - public Jid getTrueCounterpart(String counterpart) { - for (User user : this.getUsers()) { - if (user.getName().equals(counterpart)) { - return user.getJid(); - } - } - return null; + public Jid getTrueCounterpart(String name) { + User user = findUser(name); + return user == null ? null : user.getJid(); } public String getPassword() { diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Presence.java b/src/main/java/de/thedevstack/conversationsplus/entities/Presence.java new file mode 100644 index 00000000..d4f2871d --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Presence.java @@ -0,0 +1,80 @@ +package de.thedevstack.conversationsplus.entities; + +import java.lang.Comparable; +import java.util.Locale; + +import de.thedevstack.conversationsplus.xml.Element; + +public class Presence implements Comparable { + + public enum Status { + CHAT, ONLINE, AWAY, XA, DND, OFFLINE; + + public String toShowString() { + switch(this) { + case CHAT: return "chat"; + case AWAY: return "away"; + case XA: return "xa"; + case DND: return "dnd"; + } + + return null; + } + } + + protected final Status status; + protected ServiceDiscoveryResult disco; + protected final String ver; + protected final String hash; + + private Presence(Status status, String ver, String hash) { + this.status = status; + this.ver = ver; + this.hash = hash; + } + + public static Presence parse(String show, Element caps) { + final String hash = caps == null ? null : caps.getAttribute("hash"); + final String ver = caps == null ? null : caps.getAttribute("ver"); + if (show == null) { + return new Presence(Status.ONLINE, ver, hash); + } else { + switch (show.toLowerCase(Locale.US)) { + case "away": + return new Presence(Status.AWAY, ver, hash); + case "xa": + return new Presence(Status.XA, ver, hash); + case "dnd": + return new Presence(Status.DND, ver, hash); + case "chat": + return new Presence(Status.CHAT, ver, hash); + default: + return new Presence(Status.ONLINE, ver, hash); + } + } + } + + public int compareTo(Object other) { + return this.status.compareTo(((Presence)other).status); + } + + public Status getStatus() { + return this.status; + } + + public boolean hasCaps() { + return ver != null && hash != null; + } + + public String getVer() { + return this.ver; + } + + public String getHash() { + return this.hash; + } + + public void setServiceDiscoveryResult(ServiceDiscoveryResult disco) { + this.disco = disco; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Presences.java b/src/main/java/de/thedevstack/conversationsplus/entities/Presences.java index cb984648..d32e931c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Presences.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Presences.java @@ -1,29 +1,18 @@ package de.thedevstack.conversationsplus.entities; +import java.util.Collections; import java.util.Hashtable; -import java.util.Iterator; -import java.util.Map.Entry; - -import de.thedevstack.conversationsplus.xml.Element; public class Presences { + private final Hashtable<String, Presence> presences = new Hashtable<>(); - 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() { + public Hashtable<String, Presence> getPresences() { return this.presences; } - public void updatePresence(String resource, int status) { + public void updatePresence(String resource, Presence presence) { synchronized (this.presences) { - this.presences.put(resource, status); + this.presences.put(resource, presence); } } @@ -39,32 +28,10 @@ public class Presences { } } - public int getMostAvailableStatus() { - int status = OFFLINE; + public Presence getMostAvailablePresence() { synchronized (this.presences) { - 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; + if (presences.size() < 1) { return null; } + return Collections.min(presences.values()); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/ServiceDiscoveryResult.java b/src/main/java/de/thedevstack/conversationsplus/entities/ServiceDiscoveryResult.java new file mode 100644 index 00000000..cfba7c4f --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/entities/ServiceDiscoveryResult.java @@ -0,0 +1,265 @@ +package de.thedevstack.conversationsplus.entities; + +import android.content.ContentValues; +import android.database.Cursor; +import android.util.Base64; +import java.io.UnsupportedEncodingException; +import java.lang.Comparable; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.forms.Data; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +public class ServiceDiscoveryResult { + public static final String TABLENAME = "discovery_results"; + public static final String HASH = "hash"; + public static final String VER = "ver"; + public static final String RESULT = "result"; + + protected static String blankNull(String s) { + return s == null ? "" : s; + } + + public static class Identity implements Comparable { + protected final String category; + protected final String type; + protected final String lang; + protected final String name; + + public Identity(final String category, final String type, final String lang, final String name) { + this.category = category; + this.type = type; + this.lang = lang; + this.name = name; + } + + public Identity(final Element el) { + this( + el.getAttribute("category"), + el.getAttribute("type"), + el.getAttribute("xml:lang"), + el.getAttribute("name") + ); + } + + public Identity(final JSONObject o) { + this( + o.optString("category", null), + o.optString("type", null), + o.optString("lang", null), + o.optString("name", null) + ); + } + + public String getCategory() { + return this.category; + } + + public String getType() { + return this.type; + } + + public String getLang() { + return this.lang; + } + + public String getName() { + return this.name; + } + + public int compareTo(Object other) { + Identity o = (Identity)other; + int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory())); + if(r == 0) { + r = blankNull(this.getType()).compareTo(blankNull(o.getType())); + } + if(r == 0) { + r = blankNull(this.getLang()).compareTo(blankNull(o.getLang())); + } + if(r == 0) { + r = blankNull(this.getName()).compareTo(blankNull(o.getName())); + } + + return r; + } + + public JSONObject toJSON() { + try { + JSONObject o = new JSONObject(); + o.put("category", this.getCategory()); + o.put("type", this.getType()); + o.put("lang", this.getLang()); + o.put("name", this.getName()); + return o; + } catch(JSONException e) { + return null; + } + } + } + + protected final String hash; + protected final byte[] ver; + protected final List<Identity> identities; + protected final List<String> features; + protected final List<Data> forms; + + public ServiceDiscoveryResult(final IqPacket packet) { + this.identities = new ArrayList<>(); + this.features = new ArrayList<>(); + this.forms = new ArrayList<>(); + this.hash = "sha-1"; // We only support sha-1 for now + + final List<Element> elements = packet.query().getChildren(); + + for (final Element element : elements) { + if (element.getName().equals("identity")) { + Identity id = new Identity(element); + if (id.getType() != null && id.getCategory() != null) { + identities.add(id); + } + } else if (element.getName().equals("feature")) { + if (element.getAttribute("var") != null) { + features.add(element.getAttribute("var")); + } + } else if (element.getName().equals("x") && "jabber:x:data".equals(element.getAttribute("xmlns"))) { + forms.add(Data.parse(element)); + } + } + this.ver = this.mkCapHash(); + } + + public ServiceDiscoveryResult(String hash, byte[] ver, JSONObject o) throws JSONException { + this.identities = new ArrayList<>(); + this.features = new ArrayList<>(); + this.forms = new ArrayList<>(); + this.hash = hash; + this.ver = ver; + + JSONArray identities = o.optJSONArray("identities"); + if (identities != null) { + for (int i = 0; i < identities.length(); i++) { + this.identities.add(new Identity(identities.getJSONObject(i))); + } + } + JSONArray features = o.optJSONArray("features"); + if (features != null) { + for (int i = 0; i < features.length(); i++) { + this.features.add(features.getString(i)); + } + } + } + + public String getVer() { + return new String(Base64.encode(this.ver, Base64.DEFAULT)); + } + + public ServiceDiscoveryResult(Cursor cursor) throws JSONException { + this( + cursor.getString(cursor.getColumnIndex(HASH)), + Base64.decode(cursor.getString(cursor.getColumnIndex(VER)), Base64.DEFAULT), + new JSONObject(cursor.getString(cursor.getColumnIndex(RESULT))) + ); + } + + public List<Identity> getIdentities() { + return this.identities; + } + + public List<String> getFeatures() { + return this.features; + } + + public boolean hasIdentity(String category, String type) { + for(Identity id : this.getIdentities()) { + if((category == null || id.getCategory().equals(category)) && + (type == null || id.getType().equals(type))) { + return true; + } + } + + return false; + } + + protected byte[] mkCapHash() { + StringBuilder s = new StringBuilder(); + + List<Identity> identities = this.getIdentities(); + Collections.sort(identities); + + for(Identity id : identities) { + s.append( + blankNull(id.getCategory()) + "/" + + blankNull(id.getType()) + "/" + + blankNull(id.getLang()) + "/" + + blankNull(id.getName()) + "<" + ); + } + + List<String> features = this.getFeatures(); + Collections.sort(features); + + for (String feature : features) { + s.append(feature + "<"); + } + + Collections.sort(forms, new Comparator<Data>() { + @Override + public int compare(Data lhs, Data rhs) { + return lhs.getFormType().compareTo(rhs.getFormType()); + } + }); + + for(Data form : forms) { + s.append(form.getFormType()+"<"); + //TODO append fields and values + } + + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + return null; + } + + try { + return md.digest(s.toString().getBytes("UTF-8")); + } catch(UnsupportedEncodingException e) { + return null; + } + } + + public JSONObject toJSON() { + try { + JSONObject o = new JSONObject(); + + JSONArray ids = new JSONArray(); + for(Identity id : this.getIdentities()) { + ids.put(id.toJSON()); + } + o.put("identites", ids); + + o.put("features", new JSONArray(this.getFeatures())); + + return o; + } catch(JSONException e) { + return null; + } + } + + public ContentValues getContentValues() { + final ContentValues values = new ContentValues(); + values.put(HASH, this.hash); + values.put(VER, getVer()); + values.put(RESULT, this.toJSON().toString()); + return values; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Transferable.java b/src/main/java/de/thedevstack/conversationsplus/entities/Transferable.java index a5bdb5d7..b03d0fe0 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Transferable.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Transferable.java @@ -4,7 +4,7 @@ public interface Transferable { String[] VALID_IMAGE_EXTENSIONS = {"webp", "jpeg", "jpg", "png", "jpe"}; String[] VALID_CRYPTO_EXTENSIONS = {"pgp", "gpg", "otr"}; - String[] WELL_KNOWN_EXTENSIONS = {"pdf","m4a"}; + String[] WELL_KNOWN_EXTENSIONS = {"pdf","m4a","mp4"}; int STATUS_UNKNOWN = 0x200; int STATUS_CHECKING = 0x201; diff --git a/src/main/java/de/thedevstack/conversationsplus/generator/AbstractGenerator.java b/src/main/java/de/thedevstack/conversationsplus/generator/AbstractGenerator.java index 5a6c6ebf..2d825f2c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/generator/AbstractGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/generator/AbstractGenerator.java @@ -14,6 +14,7 @@ import java.util.TimeZone; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.tzur.conversations.Settings; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; public abstract class AbstractGenerator { private final String[] FEATURES = { @@ -26,15 +27,16 @@ public abstract class AbstractGenerator { "http://jabber.org/protocol/caps", "http://jabber.org/protocol/disco#info", "urn:xmpp:avatar:metadata+notify", + "http://jabber.org/protocol/nick+notify", "urn:xmpp:ping", "jabber:iq:version", - "http://jabber.org/protocol/chatstates"}; + "http://jabber.org/protocol/chatstates", + AxolotlService.PEP_DEVICE_LIST+"+notify"}; private final String[] MESSAGE_CONFIRMATION_FEATURES = { "urn:xmpp:chat-markers:0", "urn:xmpp:receipts" }; - private String mVersion = null; - public final String IDENTITY_TYPE = "phone"; + protected final String IDENTITY_TYPE = "phone"; private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); diff --git a/src/main/java/de/thedevstack/conversationsplus/generator/IqGenerator.java b/src/main/java/de/thedevstack/conversationsplus/generator/IqGenerator.java index 8f6b128f..df46f2b7 100644 --- a/src/main/java/de/thedevstack/conversationsplus/generator/IqGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/generator/IqGenerator.java @@ -1,9 +1,23 @@ package de.thedevstack.conversationsplus.generator; + +import android.util.Base64; +import android.util.Log; + +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.ecc.ECPublicKey; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; +import java.util.Set; import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.DownloadableFile; @@ -42,6 +56,34 @@ public class IqGenerator extends AbstractGenerator { return packet; } + protected IqPacket publish(final String node, final Element item) { + final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + final Element pubsub = packet.addChild("pubsub", + "http://jabber.org/protocol/pubsub"); + final Element publish = pubsub.addChild("publish"); + publish.setAttribute("node", node); + publish.addChild(item); + return packet; + } + + protected IqPacket retrieve(String node, Element item) { + final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); + final Element pubsub = packet.addChild("pubsub", + "http://jabber.org/protocol/pubsub"); + final Element items = pubsub.addChild("items"); + items.setAttribute("node", node); + if (item != null) { + items.addChild(item); + } + return packet; + } + + public IqPacket publishNick(String nick) { + final Element item = new Element("item"); + item.addChild("nick","http://jabber.org/protocol/nick").setContent(nick); + return publish("http://jabber.org/protocol/nick", item); + } + public static IqPacket retrieveVcardAvatar(final Avatar avatar) { final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); packet.setTo(avatar.owner); @@ -49,10 +91,81 @@ public class IqGenerator extends AbstractGenerator { return packet; } + public IqPacket retrieveDeviceIds(final Jid to) { + final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null); + if(to != null) { + packet.setTo(to); + } + return packet; + } + + public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) { + final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES+":"+deviceid, null); + packet.setTo(to); + return packet; + } + + public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) { + final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION+":"+deviceid, null); + packet.setTo(to); + return packet; + } + + public IqPacket publishDeviceIds(final Set<Integer> ids) { + final Element item = new Element("item"); + final Element list = item.addChild("list", AxolotlService.PEP_PREFIX); + for(Integer id:ids) { + final Element device = new Element("device"); + device.setAttribute("id", id); + list.addChild(device); + } + return publish(AxolotlService.PEP_DEVICE_LIST, item); + } + + public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, + final Set<PreKeyRecord> preKeyRecords, final int deviceId) { + final Element item = new Element("item"); + final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX); + final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic"); + signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId()); + ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey(); + signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(),Base64.DEFAULT)); + final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature"); + signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(),Base64.DEFAULT)); + final Element identityKeyElement = bundle.addChild("identityKey"); + identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); + + final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX); + for(PreKeyRecord preKeyRecord:preKeyRecords) { + final Element prekey = prekeys.addChild("preKeyPublic"); + prekey.setAttribute("preKeyId", preKeyRecord.getId()); + prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT)); + } + + return publish(AxolotlService.PEP_BUNDLES+":"+deviceId, item); + } + + public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) { + final Element item = new Element("item"); + final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX); + final Element chain = verification.addChild("chain"); + for(int i = 0; i < certificates.length; ++i) { + try { + Element certificate = chain.addChild("certificate"); + certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.DEFAULT)); + certificate.setAttribute("index",i); + } catch (CertificateEncodingException e) { + Log.d(Config.LOGTAG, "could not encode certificate"); + } + } + verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.DEFAULT)); + return publish(AxolotlService.PEP_VERIFICATION+":"+deviceId, item); + } + public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Element query = packet.query("urn:xmpp:mam:0"); - query.setAttribute("queryid",mam.getQueryId()); + query.setAttribute("queryid", mam.getQueryId()); final Data data = new Data(); data.setFormType("urn:xmpp:mam:0"); if (mam.muc()) { @@ -60,8 +173,9 @@ public class IqGenerator extends AbstractGenerator { } else if (mam.getWith()!=null) { data.put("with", mam.getWith().toString()); } - data.put("start",getTimestamp(mam.getStart())); - data.put("end",getTimestamp(mam.getEnd())); + data.put("start", getTimestamp(mam.getStart())); + data.put("end", getTimestamp(mam.getEnd())); + data.submit(); query.addChild(data); if (mam.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) { query.addChild("set", "http://jabber.org/protocol/rsm").addChild("before").setContent(mam.getReference()); @@ -130,12 +244,52 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file) { + public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) { IqPacket packet = new IqPacket(IqPacket.TYPE.GET); packet.setTo(host); - Element request = packet.addChild("request",Xmlns.HTTP_UPLOAD); + Element request = packet.addChild("request", Xmlns.HTTP_UPLOAD); request.addChild("filename").setContent(file.getName()); request.addChild("size").setContent(String.valueOf(file.getExpectedSize())); + if (mime != null) { + request.addChild("content-type").setContent(mime); + } + return packet; + } + + public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) { + final IqPacket register = new IqPacket(IqPacket.TYPE.SET); + + register.setTo(account.getServer()); + register.setId(id); + register.query("jabber:iq:register").addChild(data); + + return register; + } + + public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) { + IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + packet.setTo(appServer); + Element command = packet.addChild("command", "http://jabber.org/protocol/commands"); + command.setAttribute("node","register-push-gcm"); + command.setAttribute("action","execute"); + Data data = new Data(); + data.put("token", token); + data.put("device-id", deviceId); + data.submit(); + command.addChild(data); + return packet; + } + + public IqPacket enablePush(Jid jid, String node, String secret) { + IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + Element enable = packet.addChild("enable","urn:xmpp:push:0"); + enable.setAttribute("jid",jid.toString()); + enable.setAttribute("node", node); + Data data = new Data(); + data.setFormType("http://jabber.org/protocol/pubsub#publish-options"); + data.put("secret",secret); + data.submit(); + enable.addChild(data); return packet; } } diff --git a/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java b/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java index af496fe1..2e49ebfe 100644 --- a/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java @@ -1,14 +1,16 @@ package de.thedevstack.conversationsplus.generator; +import net.java.otr4j.OtrException; +import net.java.otr4j.session.Session; + import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; import java.util.Locale; import java.util.TimeZone; -import net.java.otr4j.OtrException; -import net.java.otr4j.session.Session; - import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlMessage; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; @@ -19,7 +21,7 @@ import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket; public class MessageGenerator extends AbstractGenerator { - private MessagePacket preparePacket(Message message, boolean addDelay) { + private MessagePacket preparePacket(Message message) { Conversation conversation = message.getConversation(); Account account = conversation.getAccount(); MessagePacket packet = new MessagePacket(); @@ -42,13 +44,13 @@ public class MessageGenerator extends AbstractGenerator { } packet.setFrom(account.getJid()); packet.setId(message.getUuid()); - if (addDelay) { - addDelay(packet, message.getTimeSent()); + if (message.edited()) { + packet.addChild("replace","urn:xmpp:message-correct:0").setAttribute("id",message.getEditedId()); } return packet; } - private void addDelay(MessagePacket packet, long timestamp) { + public 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")); @@ -57,20 +59,39 @@ public class MessageGenerator extends AbstractGenerator { delay.setAttribute("stamp", mDateFormat.format(date)); } - public MessagePacket generateOtrChat(Message message) { - return generateOtrChat(message, false); + public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) { + MessagePacket packet = preparePacket(message); + if (axolotlMessage == null) { + return null; + } + packet.setAxolotlMessage(axolotlMessage.toElement()); + packet.addChild("store", "urn:xmpp:hints"); + return packet; + } + + public static void addXhtmlImImage(MessagePacket packet, Message.FileParams params) { + Element html = packet.addChild("html", "http://jabber.org/protocol/xhtml-im"); + Element body = html.addChild("body", "http://www.w3.org/1999/xhtml"); + Element img = body.addChild("img"); + img.setAttribute("src", params.url.toString()); + img.setAttribute("height", params.height); + img.setAttribute("width", params.width); + } + + public static void addMessageHints(MessagePacket packet) { + packet.addChild("private", "urn:xmpp:carbons:2"); + packet.addChild("no-copy", "urn:xmpp:hints"); + packet.addChild("no-permanent-store", "urn:xmpp:hints"); + packet.addChild("no-permanent-storage", "urn:xmpp:hints"); //do not copy this. this is wrong. it is *store* } - public MessagePacket generateOtrChat(Message message, boolean addDelay) { + public MessagePacket generateOtrChat(Message message) { 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"); - packet.addChild("no-permanent-store", "urn:xmpp:hints"); - packet.addChild("no-permanent-storage", "urn:xmpp:hints"); + MessagePacket packet = preparePacket(message); + addMessageHints(packet); try { String content; if (message.hasFileOnRemoteHost()) { @@ -86,25 +107,24 @@ public class MessageGenerator extends AbstractGenerator { } public MessagePacket generateChat(Message message) { - return generateChat(message, false); - } - - public MessagePacket generateChat(Message message, boolean addDelay) { - MessagePacket packet = preparePacket(message, addDelay); + MessagePacket packet = preparePacket(message); + String content; if (message.hasFileOnRemoteHost()) { - packet.setBody(message.getFileParams().url.toString()); + Message.FileParams fileParams = message.getFileParams(); + content = fileParams.url.toString(); + packet.addChild("x","jabber:x:oob").addChild("url").setContent(content); + if (fileParams.width > 0 && fileParams.height > 0) { + addXhtmlImImage(packet,fileParams); + } } else { - packet.setBody(message.getBody()); + content = message.getBody(); } + packet.setBody(content); return packet; } public MessagePacket generatePgpChat(Message message) { - return generatePgpChat(message, false); - } - - public MessagePacket generatePgpChat(Message message, boolean addDelay) { - MessagePacket packet = preparePacket(message, addDelay); + MessagePacket packet = preparePacket(message); packet.setBody("This is an XEP-0027 encrypted message"); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody()); @@ -117,25 +137,26 @@ public class MessageGenerator extends AbstractGenerator { public MessagePacket generateChatState(Conversation conversation) { final Account account = conversation.getAccount(); MessagePacket packet = new MessagePacket(); + packet.setType(MessagePacket.TYPE_CHAT); packet.setTo(conversation.getJid().toBareJid()); packet.setFrom(account.getJid()); packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); + packet.addChild("no-store", "urn:xmpp:hints"); + packet.addChild("no-storage", "urn:xmpp:hints"); //wrong! don't copy this. Its *store* return packet; } public MessagePacket confirm(final Account account, final Jid to, final String id) { MessagePacket packet = new MessagePacket(); - packet.setType(MessagePacket.TYPE_NORMAL); + packet.setType(MessagePacket.TYPE_CHAT); packet.setTo(to); packet.setFrom(account.getJid()); - Element received = packet.addChild("displayed", - "urn:xmpp:chat-markers:0"); + Element received = packet.addChild("displayed", "urn:xmpp:chat-markers:0"); received.setAttribute("id", id); return packet; } - public MessagePacket conferenceSubject(Conversation conversation, - String subject) { + public MessagePacket conferenceSubject(Conversation conversation,String subject) { MessagePacket packet = new MessagePacket(); packet.setType(MessagePacket.TYPE_GROUPCHAT); packet.setTo(conversation.getJid().toBareJid()); @@ -169,14 +190,14 @@ public class MessageGenerator extends AbstractGenerator { return packet; } - public MessagePacket received(Account account, - MessagePacket originalMessage, String namespace) { + public MessagePacket received(Account account, MessagePacket originalMessage, ArrayList<String> namespaces, int type) { MessagePacket receivedPacket = new MessagePacket(); - receivedPacket.setType(MessagePacket.TYPE_NORMAL); + receivedPacket.setType(type); receivedPacket.setTo(originalMessage.getFrom()); receivedPacket.setFrom(account.getJid()); - Element received = receivedPacket.addChild("received", namespace); - received.setAttribute("id", originalMessage.getId()); + for(String namespace : namespaces) { + receivedPacket.addChild("received", namespace).setAttribute("id", originalMessage.getId()); + } return receivedPacket; } diff --git a/src/main/java/de/thedevstack/conversationsplus/generator/PresenceGenerator.java b/src/main/java/de/thedevstack/conversationsplus/generator/PresenceGenerator.java index 1656bd51..f370c6e0 100644 --- a/src/main/java/de/thedevstack/conversationsplus/generator/PresenceGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/generator/PresenceGenerator.java @@ -2,6 +2,7 @@ package de.thedevstack.conversationsplus.generator; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Contact; +import de.thedevstack.conversationsplus.entities.Presence; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.stanzas.PresencePacket; @@ -31,12 +32,14 @@ public class PresenceGenerator extends AbstractGenerator { return subscription("subscribed", contact); } - public PresencePacket sendPresence(Account account) { + public PresencePacket selfPresence(Account account, Presence.Status status) { PresencePacket packet = new PresencePacket(); + if(status.toShowString() != null) { + packet.addChild("show").setContent(status.toShowString()); + } packet.setFrom(account.getJid()); String sig = account.getPgpSignature(); if (sig != null) { - packet.addChild("status").setContent("online"); packet.addChild("x", "jabber:x:signed").setContent(sig); } String capHash = getCapHash(); diff --git a/src/main/java/de/thedevstack/conversationsplus/http/HttpConnectionManager.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpConnectionManager.java index 484e77a3..59c54662 100644 --- a/src/main/java/de/thedevstack/conversationsplus/http/HttpConnectionManager.java +++ b/src/main/java/de/thedevstack/conversationsplus/http/HttpConnectionManager.java @@ -2,6 +2,10 @@ package de.thedevstack.conversationsplus.http; import org.apache.http.conn.ssl.StrictHostnameVerifier; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.List; @@ -17,6 +21,7 @@ import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.services.AbstractConnectionManager; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.utils.CryptoHelper; +import de.thedevstack.conversationsplus.utils.SSLSocketHelper; public class HttpConnectionManager extends AbstractConnectionManager { @@ -38,9 +43,9 @@ public class HttpConnectionManager extends AbstractConnectionManager { return connection; } - public HttpUploadConnection createNewUploadConnection(Message message) { + public HttpUploadConnection createNewUploadConnection(Message message, boolean delay) { HttpUploadConnection connection = new HttpUploadConnection(this); - connection.init(message); + connection.init(message,delay); this.uploadConnections.add(connection); return connection; } @@ -70,7 +75,7 @@ public class HttpConnectionManager extends AbstractConnectionManager { new StrictHostnameVerifier()); } try { - final SSLContext sc = SSLContext.getInstance("TLS"); + final SSLContext sc = SSLSocketHelper.getSSLContext(); sc.init(null, new X509TrustManager[]{trustManager}, mXmppConnectionService.getRNG()); @@ -87,4 +92,8 @@ public class HttpConnectionManager extends AbstractConnectionManager { } catch (final KeyManagementException | NoSuchAlgorithmException ignored) { } } + + public Proxy getProxy() throws IOException { + return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getLocalHost(), 8118)); + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java index d8859df5..4381ed03 100644 --- a/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java +++ b/src/main/java/de/thedevstack/conversationsplus/http/HttpDownloadConnection.java @@ -2,30 +2,36 @@ package de.thedevstack.conversationsplus.http; import android.content.Intent; import android.net.Uri; -import android.os.SystemClock; +import android.os.PowerManager; import java.io.BufferedInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; +import java.util.concurrent.CancellationException; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLHandshakeException; import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.utils.StreamUtil; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; -import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.Transferable; +import de.thedevstack.conversationsplus.entities.TransferablePlaceholder; import de.thedevstack.conversationsplus.persistance.FileBackend; +import de.thedevstack.conversationsplus.services.AbstractConnectionManager; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.utils.CryptoHelper; -import de.thedevstack.conversationsplus.utils.MessageUtil; public class HttpDownloadConnection implements Transferable { @@ -38,7 +44,7 @@ public class HttpDownloadConnection implements Transferable { private int mStatus = Transferable.STATUS_UNKNOWN; private boolean acceptedAutomatically = false; private int mProgress = 0; - private long mLastGuiRefresh = 0; + private boolean canceled = false; public HttpDownloadConnection(HttpConnectionManager manager) { this.mHttpConnectionManager = manager; @@ -64,39 +70,45 @@ public class HttpDownloadConnection implements Transferable { } public void init(Message message, boolean interactive) { - this.message = message; - this.message.setTransferable(this); - try { - mUrl = new URL(message.getBody()); - String[] parts = mUrl.getPath().toLowerCase().split("\\."); - String lastPart = parts.length >= 1 ? parts[parts.length - 1] : null; - String secondToLast = parts.length >= 2 ? parts[parts.length -2] : null; - if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) { - this.message.setEncryption(Message.ENCRYPTION_PGP); - } else if (message.getEncryption() != Message.ENCRYPTION_OTR) { - this.message.setEncryption(Message.ENCRYPTION_NONE); - } - String extension; - if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(lastPart)) { - extension = secondToLast; - } else { - extension = lastPart; - } - message.setRelativeFilePath(message.getUuid()+"."+extension); - this.file = FileBackend.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(interactive); - } catch (MalformedURLException e) { - this.cancel(); - } + this.message = message; + this.message.setTransferable(this); + try { + if (message.hasFileOnRemoteHost()) { + mUrl = message.getFileParams().url; + } else { + mUrl = new URL(message.getBody()); + } + String[] parts = mUrl.getPath().toLowerCase().split("\\."); + String lastPart = parts.length >= 1 ? parts[parts.length - 1] : null; + String secondToLast = parts.length >= 2 ? parts[parts.length -2] : null; + if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) { + this.message.setEncryption(Message.ENCRYPTION_PGP); + } else if (message.getEncryption() != Message.ENCRYPTION_OTR + && message.getEncryption() != Message.ENCRYPTION_AXOLOTL) { + this.message.setEncryption(Message.ENCRYPTION_NONE); + } + String extension; + if (Arrays.asList(VALID_CRYPTO_EXTENSIONS).contains(lastPart)) { + extension = secondToLast; + } else { + extension = lastPart; + } + message.setRelativeFilePath(message.getUuid()+"."+extension); + this.file = FileBackend.getFile(message, false); + String reference = mUrl.getRef(); + if (reference != null && reference.length() == 96) { + this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference)); + } + + if ((this.message.getEncryption() == Message.ENCRYPTION_OTR + || this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL) + && this.file.getKey() == null) { + this.message.setEncryption(Message.ENCRYPTION_NONE); + } + checkFileSize(interactive); + } catch (MalformedURLException e) { + this.cancel(); + } } private void checkFileSize(boolean interactive) { @@ -104,21 +116,29 @@ public class HttpDownloadConnection implements Transferable { } public void cancel() { - mHttpConnectionManager.finishConnection(this); - message.setTransferable(null); - mXmppConnectionService.updateConversationUi(); + this.canceled = true; + mHttpConnectionManager.finishConnection(this); + if (message.isFileOrImage()) { + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + } else { + message.setTransferable(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.setTransferable(null); - mHttpConnectionManager.finishConnection(this); - mXmppConnectionService.updateConversationUi(); - if (acceptedAutomatically) { - mXmppConnectionService.getNotificationService().push(message); - } + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + intent.setData(Uri.fromFile(file)); + mXmppConnectionService.sendBroadcast(intent); + message.setTransferable(null); + mHttpConnectionManager.finishConnection(this); + if (message.getEncryption() == Message.ENCRYPTION_PGP) { + message.getConversation().getAccount().getPgpDecryptionService().add(message); + } + mXmppConnectionService.updateConversationUi(); + if (acceptedAutomatically) { + mXmppConnectionService.getNotificationService().push(message); + } } private void changeStatus(int status) { @@ -126,6 +146,17 @@ public class HttpDownloadConnection implements Transferable { mXmppConnectionService.updateConversationUi(); } + private void showToastForException(Exception e) { + e.printStackTrace(); + if (e instanceof java.net.UnknownHostException) { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_server_not_found); + } else if (e instanceof java.net.ConnectException) { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_connect); + } else if (!(e instanceof CancellationException)) { + mXmppConnectionService.showErrorToastInUi(R.string.download_failed_file_not_found); + } + } + private class FileSizeChecker implements Runnable { private boolean interactive = false; @@ -147,7 +178,7 @@ public class HttpDownloadConnection implements Transferable { } catch (IOException e) { Logging.d(Config.LOGTAG, "io exception in http file size checker: " + e.getMessage()); if (interactive) { - mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host); + showToastForException(e); } cancel(); return; @@ -164,31 +195,38 @@ public class HttpDownloadConnection implements Transferable { } private long retrieveFileSize() throws IOException { - Logging.d(Config.LOGTAG,"retrieve file size. interactive:"+String.valueOf(interactive)); - changeStatus(STATUS_CHECKING); - HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); - connection.setRequestMethod("HEAD"); - if (connection instanceof HttpsURLConnection) { - mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); - } - connection.connect(); - String contentLength = connection.getHeaderField("Content-Length"); - if (contentLength == null) { - return -1; - } - try { - return Long.parseLong(contentLength, 10); - } catch (NumberFormatException e) { - return -1; - } + try { + Logging.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive)); + changeStatus(STATUS_CHECKING); + HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); + connection.setRequestMethod("HEAD"); + Logging.d(Config.LOGTAG, "url: "+connection.getURL().toString()); + Logging.d(Config.LOGTAG, "connection: "+connection.toString()); + connection.setRequestProperty("User-Agent", ConversationsPlusApplication.getNameAndVersion()); + if (connection instanceof HttpsURLConnection) { + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); + } + connection.connect(); + String contentLength = connection.getHeaderField("Content-Length"); + connection.disconnect(); + if (contentLength == null) { + return -1; + } + return Long.parseLong(contentLength, 10); + } catch (IOException e) { + return -1; + } catch (NumberFormatException e) { + return -1; + } } - } private class FileDownloader implements Runnable { private boolean interactive = false; + private OutputStream os; + public FileDownloader(boolean interactive) { this.interactive = interactive; } @@ -202,37 +240,69 @@ public class HttpDownloadConnection implements Transferable { finish(); } catch (SSLHandshakeException e) { changeStatus(STATUS_OFFER); - } catch (IOException e) { - mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host); - cancel(); + } catch (Exception e) { + if (interactive) { + showToastForException(e); + } + cancel(); } } private void download() throws SSLHandshakeException, IOException { - HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); - if (connection instanceof HttpsURLConnection) { - mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); - } - connection.connect(); - BufferedInputStream is = new BufferedInputStream(connection.getInputStream()); - file.getParentFile().mkdirs(); - file.createNewFile(); - OutputStream os = file.createOutputStream(); - if (os == null) { - throw new IOException(); - } - long transmitted = 0; - long expected = file.getExpectedSize(); - int count = -1; - byte[] buffer = new byte[1024]; - while ((count = is.read(buffer)) != -1) { - transmitted += count; - os.write(buffer, 0, count); - updateProgress((int) ((((double) transmitted) / expected) * 100)); - } - os.flush(); - os.close(); - is.close(); + InputStream is = null; + PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid()); + try { + wakeLock.acquire(); + HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); + if (connection instanceof HttpsURLConnection) { + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); + } + connection.setRequestProperty("User-Agent", ConversationsPlusApplication.getNameAndVersion()); + final boolean tryResume = file.exists() && file.getKey() == null; + if (tryResume) { + Logging.d(Config.LOGTAG, "http download trying resume"); + long size = file.getSize(); + connection.setRequestProperty("Range", "bytes="+size+"-"); + } + connection.connect(); + is = new BufferedInputStream(connection.getInputStream()); + boolean serverResumed = "bytes".equals(connection.getHeaderField("Accept-Ranges")); + long transmitted = 0; + long expected = file.getExpectedSize(); + if (tryResume && serverResumed) { + Logging.d(Config.LOGTAG, "server resumed"); + transmitted = file.getSize(); + updateProgress((int) ((((double) transmitted) / expected) * 100)); + os = AbstractConnectionManager.createAppendedOutputStream(file); + } else { + file.getParentFile().mkdirs(); + file.createNewFile(); + os = AbstractConnectionManager.createOutputStream(file, true); + } + int count = -1; + byte[] buffer = new byte[1024]; + while ((count = is.read(buffer)) != -1) { + transmitted += count; + os.write(buffer, 0, count); + updateProgress((int) ((((double) transmitted) / expected) * 100)); + if (canceled) { + throw new CancellationException(); + } + } + } catch (CancellationException | IOException e) { + throw e; + } finally { + if (os != null) { + try { + os.flush(); + } catch (final IOException ignored) { + + } + } + StreamUtil.close(os); + StreamUtil.close(is); + wakeLock.release(); + } } private void updateImageBounds() { @@ -240,15 +310,11 @@ public class HttpDownloadConnection implements Transferable { MessageUtil.updateFileParams(message, mUrl); mXmppConnectionService.updateMessage(message); } - } public void updateProgress(int i) { - this.mProgress = i; - if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) { - this.mLastGuiRefresh = SystemClock.elapsedRealtime(); - mXmppConnectionService.updateConversationUi(); - } + this.mProgress = i; + mXmppConnectionService.updateConversationUi(); } @Override diff --git a/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java index 58488c99..5eab0a6b 100644 --- a/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java +++ b/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java @@ -1,7 +1,12 @@ package de.thedevstack.conversationsplus.http; import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; +import android.os.PowerManager; +import android.util.Pair; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -12,17 +17,19 @@ import java.net.URL; import javax.net.ssl.HttpsURLConnection; import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.utils.StreamUtil; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.entities.Account; -import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.persistance.FileBackend; +import de.thedevstack.conversationsplus.services.AbstractConnectionManager; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.ui.UiCallback; import de.thedevstack.conversationsplus.utils.CryptoHelper; -import de.thedevstack.conversationsplus.utils.MessageUtil; -import de.thedevstack.conversationsplus.utils.StreamUtil; import de.thedevstack.conversationsplus.utils.Xmlns; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; @@ -35,16 +42,19 @@ public class HttpUploadConnection implements Transferable { private XmppConnectionService mXmppConnectionService; private boolean canceled = false; + private boolean delayed = false; private Account account; private DownloadableFile file; private Message message; + private String mime; private URL mGetUrl; private URL mPutUrl; private byte[] key = null; private long transmitted = 0; - private long expected = 1; + + private InputStream mFileInputStream; public HttpUploadConnection(HttpConnectionManager httpConnectionManager) { this.mHttpConnectionManager = httpConnectionManager; @@ -63,12 +73,15 @@ public class HttpUploadConnection implements Transferable { @Override public long getFileSize() { - return this.file.getExpectedSize(); + return file == null ? 0 : file.getExpectedSize(); } @Override public int getProgress() { - return (int) ((((double) transmitted) / expected) * 100); + if (file == null) { + return 0; + } + return (int) ((((double) transmitted) / file.getExpectedSize()) * 100); } @Override @@ -79,25 +92,34 @@ public class HttpUploadConnection implements Transferable { private void fail() { mHttpConnectionManager.finishUploadConnection(this); message.setTransferable(null); - mXmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED); + mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + StreamUtil.close(mFileInputStream); } - public void init(Message message) { + public void init(Message message, boolean delay) { this.message = message; - message.setTransferable(this); - mXmppConnectionService.markMessage(message,Message.STATUS_UNSEND); this.account = message.getConversation().getAccount(); this.file = FileBackend.getFile(message, false); - this.file.setExpectedSize(this.file.getSize()); - - if (Config.ENCRYPT_ON_HTTP_UPLOADED) { + this.mime = this.file.getMimeType(); + this.delayed = delay; + if (Config.ENCRYPT_ON_HTTP_UPLOADED + || message.getEncryption() == Message.ENCRYPTION_AXOLOTL + || message.getEncryption() == Message.ENCRYPTION_OTR) { this.key = new byte[48]; mXmppConnectionService.getRNG().nextBytes(this.key); - this.file.setKey(this.key); + this.file.setKeyAndIv(this.key); } - + Pair<InputStream,Integer> pair; + try { + pair = AbstractConnectionManager.createInputStream(file, true); + } catch (FileNotFoundException e) { + fail(); + return; + } + this.file.setExpectedSize(pair.second); + this.mFileInputStream = pair.first; Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD); - IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file); + IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file,mime); mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -121,6 +143,8 @@ public class HttpUploadConnection implements Transferable { } } }); + message.setTransferable(this); + mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); } private class FileUploader implements Runnable { @@ -132,47 +156,51 @@ public class HttpUploadConnection implements Transferable { private void upload() { OutputStream os = null; - InputStream is = null; HttpURLConnection connection = null; + PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_upload_"+message.getUuid()); try { + wakeLock.acquire(); Logging.d(Config.LOGTAG, "uploading to " + mPutUrl.toString()); connection = (HttpURLConnection) mPutUrl.openConnection(); - if (connection instanceof HttpsURLConnection) { + + if (connection instanceof HttpsURLConnection) { mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); } connection.setRequestMethod("PUT"); connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); + connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime); + connection.setRequestProperty("User-Agent", ConversationsPlusApplication.getNameAndVersion()); connection.setDoOutput(true); connection.connect(); os = connection.getOutputStream(); - is = file.createInputStream(); transmitted = 0; - expected = file.getExpectedSize(); int count = -1; byte[] buffer = new byte[4096]; - while (((count = is.read(buffer)) != -1) && !canceled) { + while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) { transmitted += count; os.write(buffer, 0, count); mXmppConnectionService.updateConversationUi(); } os.flush(); os.close(); - is.close(); + mFileInputStream.close(); int code = connection.getResponseCode(); if (code == 200 || code == 201) { Logging.d(Config.LOGTAG, "finished uploading file"); - Message.FileParams params = message.getFileParams(); if (key != null) { mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key)); } MessageUtil.updateFileParams(message, mGetUrl); + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + intent.setData(Uri.fromFile(file)); + mXmppConnectionService.sendBroadcast(intent); message.setTransferable(null); message.setCounterpart(message.getConversation().getJid().toBareJid()); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { mXmppConnectionService.getPgpEngine().encrypt(message, new UiCallback<Message>() { @Override public void success(Message message) { - mXmppConnectionService.resendMessage(message); + mXmppConnectionService.resendMessage(message,delayed); } @Override @@ -186,7 +214,7 @@ public class HttpUploadConnection implements Transferable { } }); } else { - mXmppConnectionService.resendMessage(message); + mXmppConnectionService.resendMessage(message, delayed); } } else { fail(); @@ -195,11 +223,11 @@ public class HttpUploadConnection implements Transferable { Logging.d(Config.LOGTAG, e.getMessage()); fail(); } finally { - StreamUtil.close(is); StreamUtil.close(os); if (connection != null) { connection.disconnect(); } + wakeLock.release(); } } } diff --git a/src/main/java/de/thedevstack/conversationsplus/parser/AbstractParser.java b/src/main/java/de/thedevstack/conversationsplus/parser/AbstractParser.java index 1b520bbe..bebe41d0 100644 --- a/src/main/java/de/thedevstack/conversationsplus/parser/AbstractParser.java +++ b/src/main/java/de/thedevstack/conversationsplus/parser/AbstractParser.java @@ -10,6 +10,7 @@ import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.jid.Jid; +import de.thedevstack.conversationsplus.xmpp.stanzas.AbstractStanza; public abstract class AbstractParser { @@ -70,16 +71,13 @@ public abstract class AbstractParser { return dateFormat.parse(timestamp); } - protected void updateLastseen(final Element packet, final Account account, - final boolean presenceOverwrite) { - final Jid from = packet.getAttributeAsJid("from"); - updateLastseen(packet, account, from, presenceOverwrite); - } + protected void updateLastseen(final AbstractStanza packet, final Account account, final boolean presenceOverwrite) { + updateLastseen(getTimestamp(packet), account, packet.getFrom(), presenceOverwrite); + } - protected void updateLastseen(final Element packet, final Account account, final Jid from, final boolean presenceOverwrite) { + protected void updateLastseen(long timestamp, final Account account, final Jid from, final boolean presenceOverwrite) { final String presence = from == null || from.isBareJid() ? "" : from.getResourcepart(); final Contact contact = account.getRoster().getContact(from); - final long timestamp = getTimestamp(packet); if (timestamp >= contact.lastseen.time) { contact.lastseen.time = timestamp; if (!presence.isEmpty() && presenceOverwrite) { @@ -87,4 +85,12 @@ public abstract class AbstractParser { } } } + + protected String avatarData(Element items) { + Element item = items.findChild("item"); + if (item == null) { + return null; + } + return item.findChildContent("data", "urn:xmpp:avatar:data"); + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/parser/IqParser.java b/src/main/java/de/thedevstack/conversationsplus/parser/IqParser.java index f7f56f79..e13936c5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/parser/IqParser.java +++ b/src/main/java/de/thedevstack/conversationsplus/parser/IqParser.java @@ -1,10 +1,30 @@ package de.thedevstack.conversationsplus.parser; +import android.support.annotation.NonNull; +import android.util.Base64; +import android.util.Log; +import android.util.Pair; + +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.ecc.Curve; +import org.whispersystems.libaxolotl.ecc.ECPublicKey; +import org.whispersystems.libaxolotl.state.PreKeyBundle; + +import java.io.ByteArrayInputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.services.AvatarService; @@ -58,9 +78,201 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { mXmppConnectionService.updateRosterUi(); } + public String avatarData(final IqPacket packet) { + final Element pubsub = packet.findChild("pubsub", + "http://jabber.org/protocol/pubsub"); + if (pubsub == null) { + return null; + } + final Element items = pubsub.findChild("items"); + if (items == null) { + return null; + } + return super.avatarData(items); + } + + public Element getItem(final IqPacket packet) { + final Element pubsub = packet.findChild("pubsub", + "http://jabber.org/protocol/pubsub"); + if (pubsub == null) { + return null; + } + final Element items = pubsub.findChild("items"); + if (items == null) { + return null; + } + return items.findChild("item"); + } + + @NonNull + public Set<Integer> deviceIds(final Element item) { + Set<Integer> deviceIds = new HashSet<>(); + if (item != null) { + final Element list = item.findChild("list"); + if (list != null) { + for (Element device : list.getChildren()) { + if (!device.getName().equals("device")) { + continue; + } + try { + Integer id = Integer.valueOf(device.getAttribute("id")); + deviceIds.add(id); + } catch (NumberFormatException e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered invalid <device> node in PEP ("+e.getMessage()+"):" + device.toString()+ ", skipping..."); + continue; + } + } + } + } + return deviceIds; + } + + public Integer signedPreKeyId(final Element bundle) { + final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); + if(signedPreKeyPublic == null) { + return null; + } + return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId")); + } + + public ECPublicKey signedPreKeyPublic(final Element bundle) { + ECPublicKey publicKey = null; + final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); + if(signedPreKeyPublic == null) { + return null; + } + try { + publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(),Base64.DEFAULT), 0); + } catch (Throwable e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid signedPreKeyPublic in PEP: " + e.getMessage()); + } + return publicKey; + } + + public byte[] signedPreKeySignature(final Element bundle) { + final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature"); + if(signedPreKeySignature == null) { + return null; + } + try { + return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT); + } catch (Throwable e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : Invalid base64 in signedPreKeySignature"); + return null; + } + } + + public IdentityKey identityKey(final Element bundle) { + IdentityKey identityKey = null; + final Element identityKeyElement = bundle.findChild("identityKey"); + if(identityKeyElement == null) { + return null; + } + try { + identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0); + } catch (Throwable e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage()); + } + return identityKey; + } + + public Map<Integer, ECPublicKey> preKeyPublics(final IqPacket packet) { + Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>(); + Element item = getItem(packet); + if (item == null) { + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find <item> in bundle IQ packet: " + packet); + return null; + } + final Element bundleElement = item.findChild("bundle"); + if(bundleElement == null) { + return null; + } + final Element prekeysElement = bundleElement.findChild("prekeys"); + if(prekeysElement == null) { + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find <prekeys> in bundle IQ packet: " + packet); + return null; + } + for(Element preKeyPublicElement : prekeysElement.getChildren()) { + if(!preKeyPublicElement.getName().equals("preKeyPublic")){ + Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered unexpected tag in prekeys list: " + preKeyPublicElement); + continue; + } + Integer preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId")); + try { + ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0); + preKeyRecords.put(preKeyId, preKeyPublic); + } catch (Throwable e) { + Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping..."); + continue; + } + } + return preKeyRecords; + } + + public Pair<X509Certificate[],byte[]> verification(final IqPacket packet) { + Element item = getItem(packet); + Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null; + Element chain = verification != null ? verification.findChild("chain") : null; + Element signature = verification != null ? verification.findChild("signature") : null; + if (chain != null && signature != null) { + List<Element> certElements = chain.getChildren(); + X509Certificate[] certificates = new X509Certificate[certElements.size()]; + try { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + int i = 0; + for(Element cert : certElements) { + certificates[i] = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.getContent(),Base64.DEFAULT))); + ++i; + } + return new Pair<>(certificates,Base64.decode(signature.getContent(),Base64.DEFAULT)); + } catch (CertificateException e) { + return null; + } + } else { + return null; + } + } + + public PreKeyBundle bundle(final IqPacket bundle) { + Element bundleItem = getItem(bundle); + if(bundleItem == null) { + return null; + } + final Element bundleElement = bundleItem.findChild("bundle"); + if(bundleElement == null) { + return null; + } + ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement); + Integer signedPreKeyId = signedPreKeyId(bundleElement); + byte[] signedPreKeySignature = signedPreKeySignature(bundleElement); + IdentityKey identityKey = identityKey(bundleElement); + if(signedPreKeyPublic == null || identityKey == null) { + return null; + } + + return new PreKeyBundle(0, 0, 0, null, + signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); + } + + public List<PreKeyBundle> preKeys(final IqPacket preKeys) { + List<PreKeyBundle> bundles = new ArrayList<>(); + Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys); + if ( preKeyPublics != null) { + for (Integer preKeyId : preKeyPublics.keySet()) { + ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId); + bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic, + 0, null, null, null)); + } + } + + return bundles; + } + @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) { + if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) { + return; + } else if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) { final Element query = packet.findChild("query"); // If this is in response to a query for the whole roster: if (packet.getType() == IqPacket.TYPE.RESULT) { @@ -130,15 +342,13 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); mXmppConnectionService.sendIqPacket(account, response, null); } else { - if ((packet.getType() == IqPacket.TYPE.GET) - || (packet.getType() == IqPacket.TYPE.SET)) { + if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); final Element error = response.addChild("error"); error.setAttribute("type", "cancel"); - error.addChild("feature-not-implemented", - "urn:ietf:params:xml:ns:xmpp-stanzas"); + error.addChild("feature-not-implemented","urn:ietf:params:xml:ns:xmpp-stanzas"); account.getXmppConnection().sendIqPacket(response, null); - } + } } } diff --git a/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java b/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java index 8c7d8efd..fd89ad00 100644 --- a/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java +++ b/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java @@ -1,14 +1,25 @@ package de.thedevstack.conversationsplus.parser; +import android.util.Log; import android.util.Pair; +import de.tzur.conversations.Settings; + import net.java.otr4j.session.Session; import net.java.otr4j.session.SessionStatus; +import java.util.ArrayList; +import java.util.Set; + import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.utils.AvatarUtil; import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlServiceImpl; +import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlMessage; import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Bookmark; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; @@ -17,7 +28,6 @@ import de.thedevstack.conversationsplus.http.HttpConnectionManager; import de.thedevstack.conversationsplus.services.AvatarService; import de.thedevstack.conversationsplus.services.MessageArchiveService; import de.thedevstack.conversationsplus.services.XmppConnectionService; -import de.thedevstack.conversationsplus.utils.AvatarUtil; import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.OnMessagePacketReceived; @@ -39,6 +49,10 @@ public class MessageParser extends AbstractParser implements Jid from = packet.getFrom(); if (from.toBareJid().equals(account.getJid().toBareJid())) { conversation.setOutgoingChatState(state); + if (state == ChatState.ACTIVE || state == ChatState.COMPOSING) { + mXmppConnectionService.markRead(conversation); + account.activateGracePeriod(); + } return false; } else { return conversation.setIncomingChatState(state); @@ -72,11 +86,9 @@ public class MessageParser extends AbstractParser implements body = otrSession.transformReceiving(body); SessionStatus status = otrSession.getSessionStatus(); if (body == null && status == SessionStatus.ENCRYPTED) { - conversation.setNextEncryption(Message.ENCRYPTION_OTR); mXmppConnectionService.onOtrSessionEstablished(conversation); return null; } else if (body == null && status == SessionStatus.FINISHED) { - conversation.setNextEncryption(Message.ENCRYPTION_NONE); conversation.resetOtrSession(); mXmppConnectionService.updateConversationUi(); return null; @@ -97,6 +109,20 @@ public class MessageParser extends AbstractParser implements } } + private Message parseAxolotlChat(Element axolotlMessage, Jid from, String id, Conversation conversation, int status) { + Message finishedMessage = null; + AxolotlService service = conversation.getAccount().getAxolotlService(); + XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid()); + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage); + if(plaintextMessage != null) { + finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status); + finishedMessage.setAxolotlFingerprint(plaintextMessage.getFingerprint()); + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(finishedMessage.getConversation().getAccount())+" Received Message with session fingerprint: "+plaintextMessage.getFingerprint()); + } + + return finishedMessage; + } + private class Invite { Jid jid; String password; @@ -137,13 +163,24 @@ public class MessageParser extends AbstractParser implements return null; } + private static String extractStanzaId(Element packet, Jid by) { + for(Element child : packet.getChildren()) { + if (child.getName().equals("stanza-id") + && "urn:xmpp:sid:0".equals(child.getNamespace()) + && by.equals(child.getAttributeAsJid("by"))) { + return child.getAttribute("id"); + } + } + return null; + } + private void parseEvent(final Element event, final Jid from, final Account account) { Element items = event.findChild("items"); String node = items == null ? null : items.getAttribute("node"); if ("urn:xmpp:avatar:metadata".equals(node)) { Avatar avatar = Avatar.parseMetadata(items); if (avatar != null) { - avatar.owner = from; + avatar.owner = from.toBareJid(); if (AvatarUtil.isAvatarCached(avatar)) { if (account.getJid().toBareJid().equals(from)) { if (account.setAvatar(avatar.getFilename())) { @@ -166,13 +203,20 @@ public class MessageParser extends AbstractParser implements } else if ("http://jabber.org/protocol/nick".equals(node)) { Element i = items.findChild("item"); Element nick = i == null ? null : i.findChild("nick", "http://jabber.org/protocol/nick"); - if (nick != null) { + if (nick != null && nick.getContent() != null) { Contact contact = account.getRoster().getContact(from); contact.setPresenceName(nick.getContent()); AvatarService.getInstance().clear(account); mXmppConnectionService.updateConversationUi(); mXmppConnectionService.updateAccountUi(); } + } else if (ConversationsPlusPreferences.omemoEnabled() && AxolotlService.PEP_DEVICE_LIST.equals(node)) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account)+"Received PEP device list update from "+ from + ", processing..."); + Element item = items.findChild("item"); + Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + AxolotlService axolotlService = account.getAxolotlService(); + axolotlService.registerDevices(from, deviceIds); + mXmppConnectionService.updateAccountUi(); } } @@ -180,6 +224,13 @@ public class MessageParser extends AbstractParser implements if (packet.getType() == MessagePacket.TYPE_ERROR) { Jid from = packet.getFrom(); if (from != null) { + Element error = packet.findChild("error"); + String text = error == null ? null : error.findChildContent("text"); + if (text != null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending message to "+ from+ " failed - " + text); + } else if (error != null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending message to "+ from+ " failed - " + error); + } Message message = mXmppConnectionService.markMessage(account, from.toBareJid(), packet.getId(), @@ -201,6 +252,7 @@ public class MessageParser extends AbstractParser implements final MessagePacket packet; Long timestamp = null; final boolean isForwarded; + boolean isCarbon = false; String serverMsgId = null; final Element fin = original.findChild("fin", "urn:xmpp:mam:0"); if (fin != null) { @@ -218,7 +270,7 @@ public class MessageParser extends AbstractParser implements packet = f.first; isForwarded = true; serverMsgId = result.getAttribute("id"); - query.incrementTotalCount(); + query.incrementMessageCount(); } else if (query != null) { Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": received mam result from invalid sender"); return; @@ -231,7 +283,8 @@ public class MessageParser extends AbstractParser implements return; } timestamp = f != null ? f.second : null; - isForwarded = f != null; + isCarbon = f != null; + isForwarded = isCarbon; } else { packet = original; isForwarded = false; @@ -241,25 +294,26 @@ public class MessageParser extends AbstractParser implements timestamp = AbstractParser.getTimestamp(packet, System.currentTimeMillis()); } final String body = packet.getBody(); - final String encrypted = packet.findChildContent("x", "jabber:x:encrypted"); - final Element mucUserElement = packet.findChild("x","http://jabber.org/protocol/muc#user"); + final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); + final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted"); + final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX); int status; final Jid counterpart; final Jid to = packet.getTo(); final Jid from = packet.getFrom(); final String remoteMsgId = packet.getId(); - if (from == null || to == null) { - Logging.d(Config.LOGTAG,"no to or from in: "+packet.toString()); + if (from == null) { + Log.d(Config.LOGTAG,"no from in: "+packet.toString()); return; } boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT; - boolean isProperlyAddressed = !to.isBareJid() || account.countPresences() == 1; + boolean isProperlyAddressed = (to != null ) && (!to.isBareJid() || account.countPresences() <= 1); boolean isMucStatusMessage = from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status"); if (packet.fromAccount(account)) { status = Message.STATUS_SEND; - counterpart = to; + counterpart = to != null ? to : account.getJid(); } else { status = Message.STATUS_RECEIVED; counterpart = from; @@ -270,21 +324,21 @@ public class MessageParser extends AbstractParser implements return; } - if (extractChatState(mXmppConnectionService.find(account,from), packet)) { + if (extractChatState(mXmppConnectionService.find(account, counterpart.toBareJid()), packet)) { mXmppConnectionService.updateConversationUi(); } - if ((body != null || encrypted != null) && !isMucStatusMessage) { - Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.toBareJid(), isTypeGroupChat); + if ((body != null || pgpEncrypted != null || axolotlEncrypted != null) && !isMucStatusMessage) { + Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.toBareJid(), isTypeGroupChat, query); if (isTypeGroupChat) { if (counterpart.getResourcepart().equals(conversation.getMucOptions().getActualNick())) { status = Message.STATUS_SEND_RECEIVED; + isCarbon = true; //not really carbon but received from another resource if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status)) { return; - } else { - Message message = conversation.findSentMessageWithBody(body); + } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) { + Message message = conversation.findSentMessageWithBody(packet.getBody()); if (message != null) { - message.setRemoteMsgId(remoteMsgId); mXmppConnectionService.markMessage(message, status); return; } @@ -294,69 +348,85 @@ public class MessageParser extends AbstractParser implements } } Message message; - if (body != null && body.startsWith("?OTR")) { + if (body != null && body.startsWith("?OTR") && Config.supportOtr()) { if (!isForwarded && !isTypeGroupChat && isProperlyAddressed) { message = parseOtrChat(body, from, remoteMsgId, conversation); if (message == null) { return; } } else { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": ignoring OTR message from "+from+" isForwarded="+Boolean.toString(isForwarded)+", isProperlyAddressed="+Boolean.valueOf(isProperlyAddressed)); message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); } - } else if (encrypted != null) { - message = new Message(conversation, encrypted, Message.ENCRYPTION_PGP, status); + } else if (pgpEncrypted != null && Config.supportOpenPgp()) { + message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status); + } else if (axolotlEncrypted != null && Config.supportOmemo()) { + message = parseAxolotlChat(axolotlEncrypted, from, remoteMsgId, conversation, status); + if (message == null) { + return; + } } else { message = new Message(conversation, body, Message.ENCRYPTION_NONE, status); } + + if (serverMsgId == null) { + serverMsgId = extractStanzaId(packet, isTypeGroupChat ? conversation.getJid().toBareJid() : account.getServer()); + } + message.setCounterpart(counterpart); message.setRemoteMsgId(remoteMsgId); message.setServerMsgId(serverMsgId); + message.setCarbon(isCarbon); message.setTime(timestamp); message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0"); if (conversation.getMode() == Conversation.MODE_MULTI) { - message.setTrueCounterpart(conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart())); + Jid trueCounterpart = conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart()); + message.setTrueCounterpart(trueCounterpart); + if (trueCounterpart != null) { + updateLastseen(timestamp, account, trueCounterpart, false); + } if (!isTypeGroupChat) { message.setType(Message.TYPE_PRIVATE); } + } else { + updateLastseen(timestamp, account, packet.getFrom(), true); } - updateLastseen(packet,account,true); - boolean checkForDuplicates = serverMsgId != null + + boolean checkForDuplicates = query != null || (isTypeGroupChat && packet.hasChild("delay","urn:xmpp:delay")) || message.getType() == Message.TYPE_PRIVATE; if (checkForDuplicates && conversation.hasDuplicateMessage(message)) { - Logging.d(Config.LOGTAG,"skipping duplicate message from "+message.getCounterpart().toString()+" "+message.getBody()); + Log.d(Config.LOGTAG,"skipping duplicate message from "+message.getCounterpart().toString()+" "+message.getBody()); return; } - if (query != null) { - query.incrementMessageCount(); + + if (query != null && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) { + conversation.prepend(message); + } else { + conversation.add(message); } - conversation.add(message); - if (serverMsgId == null) { + + if (message.getEncryption() == Message.ENCRYPTION_PGP) { + conversation.getAccount().getPgpDecryptionService().add(message); + } + + if (query == null || query.getWith() == null) { //either no mam or catchup if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) { mXmppConnectionService.markRead(conversation); - account.activateGracePeriod(); + if (query == null) { + account.activateGracePeriod(); + } } else { message.markUnread(); } - mXmppConnectionService.updateConversationUi(); } - if (ConversationsPlusPreferences.confirmMessages() && remoteMsgId != null && !isForwarded) { - 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); - } + if (query == null) { + mXmppConnectionService.updateConversationUi(); } - if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().advancedStreamFeaturesLoaded()) { - if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) { - mXmppConnectionService.updateConversation(conversation); - } + + if (Settings.CONFIRM_MESSAGE_READ && remoteMsgId != null && !isForwarded && !isTypeGroupChat) { + sendMessageReceipts(account, packet); } if (message.getStatus() == Message.STATUS_RECEIVED @@ -377,15 +447,26 @@ public class MessageParser extends AbstractParser implements && mXmppConnectionService.isDownloadAllowedInConnection()) { manager.createNewDownloadConnection(message); } else { - mXmppConnectionService.getNotificationService().push(message); + if (query == null) { + mXmppConnectionService.getNotificationService().push(message); + } else if (query.getWith() == null) { // mam catchup + mXmppConnectionService.getNotificationService().pushFromBacklog(message); + } } - } else { //no body + } else if (!packet.hasChild("body")){ //no body if (isTypeGroupChat) { Conversation conversation = mXmppConnectionService.find(account, from.toBareJid()); if (packet.hasChild("subject")) { if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0); - conversation.getMucOptions().setSubject(packet.findChildContent("subject")); + String subject = packet.findChildContent("subject"); + conversation.getMucOptions().setSubject(subject); + final Bookmark bookmark = conversation.getBookmark(); + if (bookmark != null && bookmark.getBookmarkName() == null) { + if (bookmark.setBookmarkName(subject)) { + mXmppConnectionService.pushBookmarks(account); + } + } mXmppConnectionService.updateConversationUi(); return; } @@ -417,7 +498,7 @@ public class MessageParser extends AbstractParser implements mXmppConnectionService.markRead(conversation); } } else { - updateLastseen(packet, account, true); + updateLastseen(timestamp, account, packet.getFrom(), true); final Message displayedMessage = mXmppConnectionService.markMessage(account, from.toBareJid(), displayed.getAttribute("id"), Message.STATUS_SEND_DISPLAYED); Message message = displayedMessage == null ? null : displayedMessage.prev(); while (message != null @@ -440,4 +521,21 @@ public class MessageParser extends AbstractParser implements contact.setPresenceName(nick); } } + + private void sendMessageReceipts(Account account, MessagePacket packet) { + ArrayList<String> receiptsNamespaces = new ArrayList<>(); + if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) { + receiptsNamespaces.add("urn:xmpp:chat-markers:0"); + } + if (packet.hasChild("request", "urn:xmpp:receipts")) { + receiptsNamespaces.add("urn:xmpp:receipts"); + } + if (receiptsNamespaces.size() > 0) { + MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, + packet, + receiptsNamespaces, + packet.getType()); + mXmppConnectionService.sendMessagePacket(account, receipt); + } + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/parser/PresenceParser.java b/src/main/java/de/thedevstack/conversationsplus/parser/PresenceParser.java index 8bc5ec89..79ffa9bf 100644 --- a/src/main/java/de/thedevstack/conversationsplus/parser/PresenceParser.java +++ b/src/main/java/de/thedevstack/conversationsplus/parser/PresenceParser.java @@ -1,18 +1,25 @@ package de.thedevstack.conversationsplus.parser; +import android.util.Log; + import java.util.ArrayList; +import java.util.List; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.utils.AvatarUtil; +import de.thedevstack.conversationsplus.utils.UiUpdateHelper; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.crypto.PgpEngine; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.MucOptions; -import de.thedevstack.conversationsplus.entities.Presences; +import de.thedevstack.conversationsplus.entities.Presence; import de.thedevstack.conversationsplus.generator.PresenceGenerator; import de.thedevstack.conversationsplus.services.AvatarService; import de.thedevstack.conversationsplus.services.XmppConnectionService; -import de.thedevstack.conversationsplus.utils.AvatarUtil; -import de.thedevstack.conversationsplus.utils.UiUpdateHelper; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.OnPresencePacketReceived; import de.thedevstack.conversationsplus.xmpp.jid.Jid; @@ -27,19 +34,19 @@ public class PresenceParser extends AbstractParser implements } public void parseConferencePresence(PresencePacket packet, Account account) { - PgpEngine mPgpEngine = mXmppConnectionService.getPgpEngine(); final Conversation conversation = packet.getFrom() == null ? null : mXmppConnectionService.find(account, packet.getFrom().toBareJid()); if (conversation != null) { final MucOptions mucOptions = conversation.getMucOptions(); boolean before = mucOptions.online(); - int count = mucOptions.getUsers().size(); - final ArrayList<MucOptions.User> tileUserBefore = new ArrayList<>(mucOptions.getUsers().subList(0,Math.min(mucOptions.getUsers().size(),5))); - mucOptions.processPacket(packet, mPgpEngine); - final ArrayList<MucOptions.User> tileUserAfter = new ArrayList<>(mucOptions.getUsers().subList(0,Math.min(mucOptions.getUsers().size(),5))); + int count = mucOptions.getUserCount(); + final List<MucOptions.User> tileUserBefore = mucOptions.getUsers(5); + processConferencePresence(packet, mucOptions); + final List<MucOptions.User> tileUserAfter = mucOptions.getUsers(5); if (!tileUserAfter.equals(tileUserBefore)) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": update tiles for " + conversation.getName()); AvatarService.getInstance().clear(conversation); } - if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUsers().size())) { + if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUserCount())) { UiUpdateHelper.updateConversationUi(); } else if (mucOptions.online()) { UiUpdateHelper.updateMucRosterUi(); @@ -47,8 +54,120 @@ public class PresenceParser extends AbstractParser implements } } - public void parseContactPresence(PresencePacket packet, Account account) { - PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator(); + private void processConferencePresence(PresencePacket packet, MucOptions mucOptions) { + final Jid from = packet.getFrom(); + if (!from.isBareJid()) { + final String type = packet.getAttribute("type"); + final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user"); + Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update")); + final List<String> codes = getStatusCodes(x); + if (type == null) { + if (x != null) { + Element item = x.findChild("item"); + if (item != null && !from.isBareJid()) { + mucOptions.setError(MucOptions.Error.NONE); + MucOptions.User user = new MucOptions.User(mucOptions,from); + user.setAffiliation(item.getAttribute("affiliation")); + user.setRole(item.getAttribute("role")); + user.setJid(item.getAttributeAsJid("jid")); + if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(mucOptions.getConversation().getJid())) { + mucOptions.setOnline(); + mucOptions.setSelf(user); + if (mucOptions.mNickChangingInProgress) { + if (mucOptions.onRenameListener != null) { + mucOptions.onRenameListener.onSuccess(); + } + mucOptions.mNickChangingInProgress = false; + } + } else { + mucOptions.addUser(user); + } + if (mXmppConnectionService.getPgpEngine() != null) { + Element signed = packet.findChild("x", "jabber:x:signed"); + if (signed != null) { + Element status = packet.findChild("status"); + String msg = status == null ? "" : status.getContent(); + long keyId = mXmppConnectionService.getPgpEngine().fetchKeyId(mucOptions.getAccount(), msg, signed.getContent()); + if (keyId != 0) { + user.setPgpKeyId(keyId); + } + } + } + if (avatar != null) { + avatar.owner = from; + if (AvatarUtil.isAvatarCached(avatar)) { + if (user.setAvatar(avatar)) { + AvatarService.getInstance().clear(user); + } + } else { + AvatarService.getInstance().fetchAvatar(mucOptions.getAccount(), avatar); + } + } + } + } + } else if (type.equals("unavailable")) { + if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || + packet.getFrom().equals(mucOptions.getConversation().getJid())) { + if (codes.contains(MucOptions.STATUS_CODE_CHANGED_NICK)) { + mucOptions.mNickChangingInProgress = true; + } else if (codes.contains(MucOptions.STATUS_CODE_KICKED)) { + mucOptions.setError(MucOptions.Error.KICKED); + } else if (codes.contains(MucOptions.STATUS_CODE_BANNED)) { + mucOptions.setError(MucOptions.Error.BANNED); + } else if (codes.contains(MucOptions.STATUS_CODE_LOST_MEMBERSHIP)) { + mucOptions.setError(MucOptions.Error.MEMBERS_ONLY); + } else if (codes.contains(MucOptions.STATUS_CODE_AFFILIATION_CHANGE)) { + mucOptions.setError(MucOptions.Error.MEMBERS_ONLY); + } else if (codes.contains(MucOptions.STATUS_CODE_SHUTDOWN)) { + mucOptions.setError(MucOptions.Error.SHUTDOWN); + } else { + mucOptions.setError(MucOptions.Error.UNKNOWN); + Log.d(Config.LOGTAG, "unknown error in conference: " + packet); + } + } else if (!from.isBareJid()){ + MucOptions.User user = mucOptions.deleteUser(from.getResourcepart()); + if (user != null) { + AvatarService.getInstance().clear(user); + } + } + } else if (type.equals("error")) { + Element error = packet.findChild("error"); + if (error != null && error.hasChild("conflict")) { + if (mucOptions.online()) { + if (mucOptions.onRenameListener != null) { + mucOptions.onRenameListener.onFailure(); + } + } else { + mucOptions.setError(MucOptions.Error.NICK_IN_USE); + } + } else if (error != null && error.hasChild("not-authorized")) { + mucOptions.setError(MucOptions.Error.PASSWORD_REQUIRED); + } else if (error != null && error.hasChild("forbidden")) { + mucOptions.setError(MucOptions.Error.BANNED); + } else if (error != null && error.hasChild("registration-required")) { + mucOptions.setError(MucOptions.Error.BANNED); + } + } + } + } + + private static List<String> getStatusCodes(Element x) { + List<String> codes = new ArrayList<>(); + if (x != null) { + for (Element child : x.getChildren()) { + if (child.getName().equals("status")) { + String code = child.getAttribute("code"); + if (code != null) { + codes.add(code); + } + } + } + } + return codes; + } + + public void parseContactPresence(final PresencePacket packet, final Account account) { + final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator(); final Jid from = packet.getFrom(); if (from == null) { return; @@ -56,7 +175,7 @@ public class PresenceParser extends AbstractParser implements final String type = packet.getAttribute("type"); final Contact contact = account.getRoster().getContact(from); if (type == null) { - String presence = from.isBareJid() ? "" : from.getResourcepart(); + final String resource = from.isBareJid() ? "" : from.getResourcepart(); contact.setPresenceName(packet.findChildContent("nick", "http://jabber.org/protocol/nick")); Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update")); if (avatar != null && !contact.isSelf()) { @@ -72,7 +191,15 @@ public class PresenceParser extends AbstractParser implements } } int sizeBefore = contact.getPresences().size(); - contact.updatePresence(presence, Presences.parseShow(packet.findChild("show"))); + + final String show = packet.findChildContent("show"); + final Element caps = packet.findChild("c", "http://jabber.org/protocol/caps"); + final Presence presence = Presence.parse(show, caps); + contact.updatePresence(resource, presence); + if (presence.hasCaps() && Config.REQUEST_DISCO) { + mXmppConnectionService.fetchCaps(account, from, presence); + } + PgpEngine pgp = mXmppConnectionService.getPgpEngine(); Element x = packet.findChild("x", "jabber:x:signed"); if (pgp != null && x != null) { @@ -96,6 +223,19 @@ public class PresenceParser extends AbstractParser implements mPresenceGenerator.sendPresenceUpdatesTo(contact)); } else { contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST); + final Conversation conversation = mXmppConnectionService.findOrCreateConversation( + account, contact.getJid().toBareJid(), false); + final String statusMessage = packet.findChildContent("status"); + if (statusMessage != null + && !statusMessage.isEmpty() + && conversation.countMessages() == 0) { + conversation.add(new Message( + conversation, + statusMessage, + Message.ENCRYPTION_NONE, + Message.STATUS_RECEIVED + )); + } } } UiUpdateHelper.updateRosterUi(); diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java index 5b09fe2e..52f60e88 100644 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java @@ -1,31 +1,58 @@ package de.thedevstack.conversationsplus.persistance; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteCantOpenDatabaseException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Base64; +import android.util.Log; +import android.util.Pair; + +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.IdentityKeyPair; +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SessionRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import org.json.JSONException; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlServiceImpl; +import de.thedevstack.conversationsplus.crypto.axolotl.SQLiteAxolotlStore; +import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.Roster; +import de.thedevstack.conversationsplus.entities.ServiceDiscoveryResult; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; -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 = 14; + private static final int DATABASE_VERSION = 24; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -33,12 +60,75 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Contact.JID + " TEXT," + Contact.KEYS + " TEXT," + Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER," + Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, " - + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, " + + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, " + Contact.GROUPS + " TEXT, FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", " + Contact.JID + ") ON CONFLICT REPLACE);"; + private static String CREATE_DISCOVERY_RESULTS_STATEMENT = "create table " + + ServiceDiscoveryResult.TABLENAME + "(" + + ServiceDiscoveryResult.HASH + " TEXT, " + + ServiceDiscoveryResult.VER + " TEXT, " + + ServiceDiscoveryResult.RESULT + " TEXT, " + + "UNIQUE(" + ServiceDiscoveryResult.HASH + ", " + + ServiceDiscoveryResult.VER + ") ON CONFLICT REPLACE);"; + + private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE " + + SQLiteAxolotlStore.PREKEY_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.ID + + ") ON CONFLICT REPLACE" + + ");"; + + private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE " + + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.ID + + ") ON CONFLICT REPLACE" + + ");"; + + private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE " + + SQLiteAxolotlStore.SESSION_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.NAME + " TEXT, " + + SQLiteAxolotlStore.DEVICE_ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.NAME + ", " + + SQLiteAxolotlStore.DEVICE_ID + + ") ON CONFLICT REPLACE" + + ");"; + + private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE " + + SQLiteAxolotlStore.IDENTITIES_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.NAME + " TEXT, " + + SQLiteAxolotlStore.OWN + " INTEGER, " + + SQLiteAxolotlStore.FINGERPRINT + " TEXT, " + + SQLiteAxolotlStore.CERTIFICATE + " BLOB, " + + SQLiteAxolotlStore.TRUSTED + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.NAME + ", " + + SQLiteAxolotlStore.FINGERPRINT + + ") ON CONFLICT IGNORE" + + ");"; + private DatabaseBackend(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @@ -49,9 +139,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID + " TEXT PRIMARY KEY," + Account.USERNAME + " TEXT," + Account.SERVER + " TEXT," + Account.PASSWORD + " TEXT," + + Account.DISPLAY_NAME + " TEXT, " + Account.ROSTERVERSION + " TEXT," + Account.OPTIONS + " NUMBER, " + Account.AVATAR + " TEXT, " + Account.KEYS - + " TEXT)"); + + " TEXT, " + Account.HOSTNAME + " TEXT, " + Account.PORT + " NUMBER DEFAULT 5222)"); db.execSQL("create table " + Conversation.TABLENAME + " (" + Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME + " TEXT, " + Conversation.CONTACT + " TEXT, " @@ -69,12 +160,21 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, " + Message.RELATIVE_FILE_PATH + " TEXT, " + Message.SERVER_MSG_ID + " TEXT, " + + Message.FINGERPRINT + " TEXT, " + + Message.CARBON + " INTEGER, " + + Message.EDITED + " TEXT, " + + Message.READ + " NUMBER DEFAULT 1, " + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY(" + Message.CONVERSATION + ") REFERENCES " + Conversation.TABLENAME + "(" + Conversation.UUID + ") ON DELETE CASCADE);"); db.execSQL(CREATE_CONTATCS_STATEMENT); + db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT); + db.execSQL(CREATE_SESSIONS_STATEMENT); + db.execSQL(CREATE_PREKEYS_STATEMENT); + db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); + db.execSQL(CREATE_IDENTITIES_STATEMENT); } @Override @@ -109,12 +209,12 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN " + Conversation.ATTRIBUTES + " TEXT"); } - if (oldVersion < 9 && newVersion >= 9) { - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.LAST_TIME + " NUMBER"); - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.LAST_PRESENCE + " TEXT"); - } + if (oldVersion < 9 && newVersion >= 9) { + db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + + Contact.LAST_TIME + " NUMBER"); + db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + + Contact.LAST_PRESENCE + " TEXT"); + } if (oldVersion < 10 && newVersion >= 10) { db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.RELATIVE_FILE_PATH + " TEXT"); @@ -122,23 +222,23 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 11 && newVersion >= 11) { db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.GROUPS + " TEXT"); - db.execSQL("delete from "+Contact.TABLENAME); - db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL"); + db.execSQL("delete from " + Contact.TABLENAME); + db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL"); } if (oldVersion < 12 && newVersion >= 12) { db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.SERVER_MSG_ID + " TEXT"); } if (oldVersion < 13 && newVersion >= 13) { - db.execSQL("delete from "+Contact.TABLENAME); - db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL"); + db.execSQL("delete from " + Contact.TABLENAME); + db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL"); } if (oldVersion < 14 && newVersion >= 14) { // migrate db to new, canonicalized JID domainpart representation // Conversation table Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME, new String[0]); - while(cursor.moveToNext()) { + while (cursor.moveToNext()) { String newJid; try { newJid = Jid.fromString( @@ -146,8 +246,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { ).toString(); } catch (InvalidJidException ignored) { Logging.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID " - +cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) - +": " + ignored +". Skipping..."); + + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) + + ": " + ignored + ". Skipping..."); continue; } @@ -156,14 +256,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { cursor.getString(cursor.getColumnIndex(Conversation.UUID)), }; db.execSQL("update " + Conversation.TABLENAME - + " set " + Conversation.CONTACTJID + " = ? " + + " set " + Conversation.CONTACTJID + " = ? " + " where " + Conversation.UUID + " = ?", updateArgs); } cursor.close(); // Contact table cursor = db.rawQuery("select * from " + Contact.TABLENAME, new String[0]); - while(cursor.moveToNext()) { + while (cursor.moveToNext()) { String newJid; try { newJid = Jid.fromString( @@ -171,8 +271,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { ).toString(); } catch (InvalidJidException ignored) { Logging.e(Config.LOGTAG, "Failed to migrate Contact JID " - +cursor.getString(cursor.getColumnIndex(Contact.JID)) - +": " + ignored +". Skipping..."); + + cursor.getString(cursor.getColumnIndex(Contact.JID)) + + ": " + ignored + ". Skipping..."); continue; } @@ -190,7 +290,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { // Account table cursor = db.rawQuery("select * from " + Account.TABLENAME, new String[0]); - while(cursor.moveToNext()) { + while (cursor.moveToNext()) { String newServer; try { newServer = Jid.fromParts( @@ -200,8 +300,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { ).getDomainpart(); } catch (InvalidJidException ignored) { Logging.e(Config.LOGTAG, "Failed to migrate Account SERVER " - +cursor.getString(cursor.getColumnIndex(Account.SERVER)) - +": " + ignored +". Skipping..."); + + cursor.getString(cursor.getColumnIndex(Account.SERVER)) + + ": " + ignored + ". Skipping..."); continue; } @@ -215,6 +315,67 @@ public class DatabaseBackend extends SQLiteOpenHelper { } cursor.close(); } + if (oldVersion < 15 && newVersion >= 15) { + recreateAxolotlDb(db); + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.FINGERPRINT + " TEXT"); + } + if (oldVersion < 16 && newVersion >= 16) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.CARBON + " INTEGER"); + } + if (oldVersion < 19 && newVersion >= 19) { + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.DISPLAY_NAME + " TEXT"); + } + if (oldVersion < 20 && newVersion >= 20) { + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT"); + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222"); + } + /* Any migrations that alter the Account table need to happen BEFORE this migration, as it + * depends on account de-serialization. + */ + if (oldVersion < 17 && newVersion >= 17) { + List<Account> accounts = getAccounts(db); + for (Account account : accounts) { + String ownDeviceIdString = account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID); + if (ownDeviceIdString == null) { + continue; + } + int ownDeviceId = Integer.valueOf(ownDeviceIdString); + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), ownDeviceId); + deleteSession(db, account, ownAddress); + IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account); + if (identityKeyPair != null) { + setIdentityKeyTrust(db, account, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), XmppAxolotlSession.Trust.TRUSTED); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not load own identity key pair"); + } + } + } + if (oldVersion < 18 && newVersion >= 18) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ + " NUMBER DEFAULT 1"); + } + + if (oldVersion < 21 && newVersion >= 21) { + List<Account> accounts = getAccounts(db); + for (Account account : accounts) { + account.unsetPgpSignature(); + db.update(Account.TABLENAME, account.getContentValues(), Account.UUID + + "=?", new String[]{account.getUuid()}); + } + } + + if (oldVersion < 22 && oldVersion >= 15 && newVersion >= 22) { + db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE); + } + + if (oldVersion < 23 && newVersion >= 23) { + db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT); + } + + if (oldVersion < 24 && newVersion >= 24) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT"); + } } public static synchronized DatabaseBackend getInstance(Context context) { @@ -239,26 +400,34 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.insert(Account.TABLENAME, null, account.getContentValues()); } - public void createContact(Contact contact) { + public void insertDiscoveryResult(ServiceDiscoveryResult result) { SQLiteDatabase db = this.getWritableDatabase(); - db.insert(Contact.TABLENAME, null, contact.getContentValues()); + db.insert(ServiceDiscoveryResult.TABLENAME, null, result.getContentValues()); } - public int getConversationCount() { + public ServiceDiscoveryResult findDiscoveryResult(final String hash, final String ver) { SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.rawQuery("select count(uuid) as count from " - + Conversation.TABLENAME + " where " + Conversation.STATUS - + "=" + Conversation.STATUS_AVAILABLE, null); + String[] selectionArgs = {hash, ver}; + Cursor cursor = db.query(ServiceDiscoveryResult.TABLENAME, null, + ServiceDiscoveryResult.HASH + "=? AND " + ServiceDiscoveryResult.VER + "=?", + selectionArgs, null, null, null); + if (cursor.getCount() == 0) + return null; cursor.moveToFirst(); - int count = cursor.getInt(0); + + ServiceDiscoveryResult result = null; + try { + result = new ServiceDiscoveryResult(cursor); + } catch (JSONException e) { /* result is still null */ } + cursor.close(); - return count; + return result; } public CopyOnWriteArrayList<Conversation> getConversations(int status) { CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = { Integer.toString(status) }; + String[] selectionArgs = {Integer.toString(status)}; Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME + " where " + Conversation.STATUS + " = ? order by " + Conversation.CREATED + " desc", selectionArgs); @@ -274,20 +443,20 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public ArrayList<Message> getMessages(Conversation conversation, int limit, - long timestamp) { + long timestamp) { ArrayList<Message> list = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor; if (timestamp == -1) { - String[] selectionArgs = { conversation.getUuid() }; + 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) }; + String[] selectionArgs = {conversation.getUuid(), + Long.toString(timestamp)}; cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION - + "=? and " + Message.TIME_SENT + "<?", selectionArgs, + + "=? and " + Message.TIME_SENT + "<?", selectionArgs, null, null, Message.TIME_SENT + " DESC", String.valueOf(limit)); } @@ -303,15 +472,52 @@ public class DatabaseBackend extends SQLiteOpenHelper { return list; } + public Iterable<Message> getMessagesIterable(final Conversation conversation) { + return new Iterable<Message>() { + @Override + public Iterator<Message> iterator() { + class MessageIterator implements Iterator<Message> { + SQLiteDatabase db = getReadableDatabase(); + String[] selectionArgs = {conversation.getUuid()}; + Cursor cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION + + "=?", selectionArgs, null, null, Message.TIME_SENT + + " ASC", null); + + public MessageIterator() { + cursor.moveToFirst(); + } + + @Override + public boolean hasNext() { + return !cursor.isAfterLast(); + } + + @Override + public Message next() { + Message message = Message.fromCursor(cursor); + cursor.moveToNext(); + return message; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + return new MessageIterator(); + } + }; + } + public Conversation findConversation(final Account account, final Jid contactJid) { SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = { account.getUuid(), + String[] selectionArgs = {account.getUuid(), contactJid.toBareJid().toString() + "/%", contactJid.toBareJid().toString() - }; + }; Cursor cursor = db.query(Conversation.TABLENAME, null, Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID - + " like ? OR "+Conversation.CONTACTJID+"=?)", selectionArgs, null, null, null); + + " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null); if (cursor.getCount() == 0) return null; cursor.moveToFirst(); @@ -322,14 +528,18 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void updateConversation(final Conversation conversation) { final SQLiteDatabase db = this.getWritableDatabase(); - final String[] args = { conversation.getUuid() }; + final String[] args = {conversation.getUuid()}; db.update(Conversation.TABLENAME, conversation.getContentValues(), Conversation.UUID + "=?", args); } public List<Account> getAccounts() { - List<Account> list = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); + return getAccounts(db); + } + + private List<Account> getAccounts(SQLiteDatabase db) { + List<Account> list = new ArrayList<>(); Cursor cursor = db.query(Account.TABLENAME, null, null, null, null, null, null); while (cursor.moveToNext()) { @@ -341,14 +551,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void updateAccount(Account account) { SQLiteDatabase db = this.getWritableDatabase(); - String[] args = { account.getUuid() }; + 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() }; + String[] args = {account.getUuid()}; db.delete(Account.TABLENAME, Account.UUID + "=?", args); } @@ -377,7 +587,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void updateMessage(Message message) { SQLiteDatabase db = this.getWritableDatabase(); - String[] args = { message.getUuid() }; + String[] args = {message.getUuid()}; + db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + + "=?", args); + } + + public void updateMessage(Message message, String uuid) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {uuid}; db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + "=?", args); } @@ -385,7 +602,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void readRoster(Roster roster) { SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor; - String args[] = { roster.getAccount().getUuid() }; + 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)); @@ -396,89 +613,517 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void writeRoster(final Roster roster) { final Account account = roster.getAccount(); final SQLiteDatabase db = this.getWritableDatabase(); + db.beginTransaction(); 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().toString() }; + String[] whereArgs = {account.getUuid(), contact.getJid().toString()}; db.delete(Contact.TABLENAME, where, whereArgs); } } + db.setTransactionSuccessful(); + db.endTransaction(); 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() }; + 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) { + public Pair<Long, String> getLastMessageReceived(Account account) { + try { + SQLiteDatabase db = this.getReadableDatabase(); + String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1"; + String[] args = {account.getUuid()}; + Cursor cursor = db.rawQuery(sql, args); + if (cursor.getCount() == 0) { + return null; + } else { + cursor.moveToFirst(); + return new Pair<>(cursor.getLong(0), cursor.getString(1)); + } + } catch (Exception e) { return null; } - cursor.moveToFirst(); - Conversation conversation = Conversation.fromCursor(cursor); + } + + private Cursor getCursorForSession(Account account, AxolotlAddress contact) { + final SQLiteDatabase db = this.getReadableDatabase(); + String[] columns = null; + String[] selectionArgs = {account.getUuid(), + contact.getName(), + Integer.toString(contact.getDeviceId())}; + Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ? AND " + + SQLiteAxolotlStore.DEVICE_ID + " = ? ", + selectionArgs, + null, null, null); + + return cursor; + } + + public SessionRecord loadSession(Account account, AxolotlAddress contact) { + SessionRecord session = null; + Cursor cursor = getCursorForSession(account, contact); + if (cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + } catch (IOException e) { + cursor.close(); + throw new AssertionError(e); + } + } cursor.close(); - return conversation; + return session; + } + + public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) { + final SQLiteDatabase db = this.getReadableDatabase(); + return getSubDeviceSessions(db, account, contact); } - public Message findMessageByUuid(String messageUuid) { + private List<Integer> getSubDeviceSessions(SQLiteDatabase db, Account account, AxolotlAddress contact) { + List<Integer> devices = new ArrayList<>(); + String[] columns = {SQLiteAxolotlStore.DEVICE_ID}; + String[] selectionArgs = {account.getUuid(), + contact.getName()}; + Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ?", + selectionArgs, + null, null, null); + + while (cursor.moveToNext()) { + devices.add(cursor.getInt( + cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID))); + } + + cursor.close(); + return devices; + } + + public boolean containsSession(Account account, AxolotlAddress contact) { + Cursor cursor = getCursorForSession(account, contact); + int count = cursor.getCount(); + cursor.close(); + return count != 0; + } + + public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.NAME, contact.getName()); + values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values); + } + + public void deleteSession(Account account, AxolotlAddress contact) { + SQLiteDatabase db = this.getWritableDatabase(); + deleteSession(db, account, contact); + } + + private void deleteSession(SQLiteDatabase db, Account account, AxolotlAddress contact) { + String[] args = {account.getUuid(), + contact.getName(), + Integer.toString(contact.getDeviceId())}; + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ? AND " + + SQLiteAxolotlStore.DEVICE_ID + " = ? ", + args); + } + + public void deleteAllSessions(Account account, AxolotlAddress contact) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid(), contact.getName()}; + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.NAME + " = ?", + args); + } + + private Cursor getCursorForPreKey(Account account, int preKeyId) { 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; + String[] columns = {SQLiteAxolotlStore.KEY}; + String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)}; + Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", + selectionArgs, + null, null, null); + + return cursor; + } + + public PreKeyRecord loadPreKey(Account account, int preKeyId) { + PreKeyRecord record = null; + Cursor cursor = getCursorForPreKey(account, preKeyId); + if (cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + } catch (IOException e) { + throw new AssertionError(e); + } } - cursor.moveToFirst(); - Message message = Message.fromCursor(cursor); cursor.close(); - return message; + return record; } - public Account findAccountByUuid(String accountUuid) { + public boolean containsPreKey(Account account, int preKeyId) { + Cursor cursor = getCursorForPreKey(account, preKeyId); + int count = cursor.getCount(); + cursor.close(); + return count != 0; + } + + public void storePreKey(Account account, PreKeyRecord record) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.ID, record.getId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values); + } + + public void deletePreKey(Account account, int preKeyId) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid(), Integer.toString(preKeyId)}; + db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", + args); + } + + private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) { 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; + String[] columns = {SQLiteAxolotlStore.KEY}; + String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)}; + Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?", + selectionArgs, + null, null, null); + + return cursor; + } + + public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) { + SignedPreKeyRecord record = null; + Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId); + if (cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + } catch (IOException e) { + throw new AssertionError(e); + } } - cursor.moveToFirst(); - Account account = Account.fromCursor(cursor); cursor.close(); - return account; + return record; } - public List<Message> getImageMessages(Conversation conversation) { - ArrayList<Message> list = new ArrayList<>(); + public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) { + List<SignedPreKeyRecord> prekeys = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor; - String[] selectionArgs = { conversation.getUuid(), String.valueOf(Message.TYPE_IMAGE) }; - cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION - + "=? AND "+Message.TYPE+"=?", selectionArgs, null, null,null); + String[] columns = {SQLiteAxolotlStore.KEY}; + String[] selectionArgs = {account.getUuid()}; + Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + "=?", + selectionArgs, + null, null, null); + + while (cursor.moveToNext()) { + try { + prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT))); + } catch (IOException ignored) { + } + } + cursor.close(); + return prekeys; + } + + public boolean containsSignedPreKey(Account account, int signedPreKeyId) { + Cursor cursor = getCursorForPreKey(account, signedPreKeyId); + int count = cursor.getCount(); + cursor.close(); + return count != 0; + } + + public void storeSignedPreKey(Account account, SignedPreKeyRecord record) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.ID, record.getId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values); + } + + public void deleteSignedPreKey(Account account, int signedPreKeyId) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)}; + db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", + args); + } + + private Cursor getIdentityKeyCursor(Account account, String name, boolean own) { + final SQLiteDatabase db = this.getReadableDatabase(); + return getIdentityKeyCursor(db, account, name, own); + } + + private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) { + return getIdentityKeyCursor(db, account, name, own, null); + } + + private Cursor getIdentityKeyCursor(Account account, String fingerprint) { + final SQLiteDatabase db = this.getReadableDatabase(); + return getIdentityKeyCursor(db, account, fingerprint); + } + + private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String fingerprint) { + return getIdentityKeyCursor(db, account, null, null, fingerprint); + } + + private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) { + String[] columns = {SQLiteAxolotlStore.TRUSTED, + SQLiteAxolotlStore.KEY}; + ArrayList<String> selectionArgs = new ArrayList<>(4); + selectionArgs.add(account.getUuid()); + String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?"; + if (name != null) { + selectionArgs.add(name); + selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?"; + } + if (fingerprint != null) { + selectionArgs.add(fingerprint); + selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?"; + } + if (own != null) { + selectionArgs.add(own ? "1" : "0"); + selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?"; + } + Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, + columns, + selectionString, + selectionArgs.toArray(new String[selectionArgs.size()]), + null, null, null); + + return cursor; + } + + public IdentityKeyPair loadOwnIdentityKeyPair(Account account) { + SQLiteDatabase db = getReadableDatabase(); + return loadOwnIdentityKeyPair(db, account); + } + + private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) { + String name = account.getJid().toBareJid().toString(); + IdentityKeyPair identityKeyPair = null; + Cursor cursor = getIdentityKeyCursor(db, account, name, true); + if (cursor.getCount() != 0) { + cursor.moveToFirst(); + try { + identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); + } + } + cursor.close(); + + return identityKeyPair; + } + + public Set<IdentityKey> loadIdentityKeys(Account account, String name) { + return loadIdentityKeys(account, name, null); + } + + public Set<IdentityKey> loadIdentityKeys(Account account, String name, XmppAxolotlSession.Trust trust) { + Set<IdentityKey> identityKeys = new HashSet<>(); + Cursor cursor = getIdentityKeyCursor(account, name, false); + + while (cursor.moveToNext()) { + if (trust != null && + cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED)) + != trust.getCode()) { + continue; + } + try { + identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT), 0)); + } catch (InvalidKeyException e) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); + } + } + cursor.close(); + + return identityKeys; + } + + public long numTrustedKeys(Account account, String name) { + SQLiteDatabase db = getReadableDatabase(); + String[] args = { + account.getUuid(), + name, + String.valueOf(XmppAxolotlSession.Trust.TRUSTED.getCode()), + String.valueOf(XmppAxolotlSession.Trust.TRUSTED_X509.getCode()) + }; + return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?" + + " AND " + SQLiteAxolotlStore.NAME + " = ?" + + " AND (" + SQLiteAxolotlStore.TRUSTED + " = ? OR " + SQLiteAxolotlStore.TRUSTED + " = ?)", + args + ); + } + + private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) { + storeIdentityKey(account, name, own, fingerprint, base64Serialized, XmppAxolotlSession.Trust.UNDECIDED); + } + + private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, XmppAxolotlSession.Trust trusted) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + values.put(SQLiteAxolotlStore.NAME, name); + values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0); + values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint); + values.put(SQLiteAxolotlStore.KEY, base64Serialized); + values.put(SQLiteAxolotlStore.TRUSTED, trusted.getCode()); + db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); + } + + public XmppAxolotlSession.Trust isIdentityKeyTrusted(Account account, String fingerprint) { + Cursor cursor = getIdentityKeyCursor(account, fingerprint); + XmppAxolotlSession.Trust trust = null; if (cursor.getCount() > 0) { - cursor.moveToLast(); - do { - Message message = Message.fromCursor(cursor); - message.setConversation(conversation); - list.add(message); - } while (cursor.moveToPrevious()); + cursor.moveToFirst(); + int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED)); + trust = XmppAxolotlSession.Trust.fromCode(trustValue); } cursor.close(); - return list; + return trust; + } + + public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) { + SQLiteDatabase db = this.getWritableDatabase(); + return setIdentityKeyTrust(db, account, fingerprint, trust); + } + + private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, XmppAxolotlSession.Trust trust) { + String[] selectionArgs = { + account.getUuid(), + fingerprint + }; + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode()); + int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.FINGERPRINT + " = ? ", + selectionArgs); + return rows == 1; + } + + public boolean setIdentityKeyCertificate(Account account, String fingerprint, X509Certificate x509Certificate) { + SQLiteDatabase db = this.getWritableDatabase(); + String[] selectionArgs = { + account.getUuid(), + fingerprint + }; + try { + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.CERTIFICATE, x509Certificate.getEncoded()); + return db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.FINGERPRINT + " = ? ", + selectionArgs) == 1; + } catch (CertificateEncodingException e) { + Log.d(Config.LOGTAG, "could not encode certificate"); + return false; + } + } + + public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) { + SQLiteDatabase db = this.getReadableDatabase(); + String[] selectionArgs = { + account.getUuid(), + fingerprint + }; + String[] colums = {SQLiteAxolotlStore.CERTIFICATE}; + String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? "; + Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null); + if (cursor.getCount() < 1) { + return null; + } else { + cursor.moveToFirst(); + byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE)); + if (certificate == null || certificate.length == 0) { + return null; + } + try { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate)); + } catch (CertificateException e) { + Log.d(Config.LOGTAG,"certificate exception "+e.getMessage()); + return null; + } + } + } + + public void storeIdentityKey(Account account, String name, IdentityKey identityKey) { + storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT)); + } + + public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) { + storeIdentityKey(account, account.getJid().toBareJid().toString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED); + } + + public void recreateAxolotlDb() { + recreateAxolotlDb(getWritableDatabase()); + } + + public void recreateAxolotlDb(SQLiteDatabase db) { + Log.d(Config.LOGTAG, AxolotlServiceImpl.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<"); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME); + db.execSQL(CREATE_SESSIONS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME); + db.execSQL(CREATE_PREKEYS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME); + db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME); + db.execSQL(CREATE_IDENTITIES_STATEMENT); + } + + public void wipeAxolotlDb(Account account) { + String accountName = account.getUuid(); + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<"); + SQLiteDatabase db = this.getWritableDatabase(); + String[] deleteArgs = { + accountName + }; + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); + db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", + deleteArgs); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java index d58d0583..611f573c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java +++ b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java @@ -1,38 +1,32 @@ package de.thedevstack.conversationsplus.persistance; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Environment; +import android.util.Log; +import android.webkit.MimeTypeMap; + 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.net.URL; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.Date; import java.util.Locale; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Environment; -import android.webkit.MimeTypeMap; - import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.exceptions.FileCopyException; +import de.thedevstack.conversationsplus.utils.StreamUtil; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; -import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.exceptions.FileCopyException; -import de.thedevstack.conversationsplus.utils.ImageUtil; -import de.thedevstack.conversationsplus.utils.MessageUtil; -import de.thedevstack.conversationsplus.utils.StreamUtil; - -public final class FileBackend { +public class FileBackend { private static final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); public static DownloadableFile getFile(Message message) { @@ -40,34 +34,28 @@ public final class FileBackend { } public static DownloadableFile getFile(Message message, boolean decrypted) { + final boolean encrypted = !decrypted + && (message.getEncryption() == Message.ENCRYPTION_PGP + || message.getEncryption() == Message.ENCRYPTION_DECRYPTED); + final DownloadableFile file; String path = message.getRelativeFilePath(); - String extension; - if (path != null && !path.isEmpty()) { - String[] parts = path.split("\\."); - extension = "."+parts[parts.length - 1]; + if (path == null) { + path = message.getUuid(); + } + if (path.startsWith("/")) { + file = new DownloadableFile(path); } else { - if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_TEXT) { - extension = ".png"; + String mime = message.getMimeType(); + if (mime != null && mime.startsWith("image")) { + file = new DownloadableFile(getConversationsImageDirectory() + path); } else { - extension = ""; + file = new DownloadableFile(getConversationsFileDirectory() + path); } - path = message.getUuid()+extension; } - final boolean encrypted = !decrypted - && (message.getEncryption() == Message.ENCRYPTION_PGP - || message.getEncryption() == Message.ENCRYPTION_DECRYPTED); if (encrypted) { - return new DownloadableFile(getConversationsFileDirectory()+message.getUuid()+extension+".pgp"); + return new DownloadableFile(getConversationsFileDirectory() + file.getName() + ".pgp"); } else { - if (path.startsWith("/")) { - return new DownloadableFile(path); - } else { - if (Arrays.asList(Transferable.VALID_IMAGE_EXTENSIONS).contains(extension)) { - return new DownloadableFile(getConversationsImageDirectory() + path); - } else { - return new DownloadableFile(getConversationsFileDirectory() + path); - } - } + return file; } } @@ -87,12 +75,7 @@ public final class FileBackend { return FileBackend.getPrivateFileDirectoryPath() + File.separator + "Images" + File.separator; } - public static DownloadableFile copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { - Logging.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage"); - String mime = ConversationsPlusApplication.getInstance().getContentResolver().getType(uri); - String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); - message.setRelativeFilePath(FileBackend.getPrivateFileDirectoryPath() + message.getUuid() + "." + extension); - DownloadableFile file = getFile(message); + public static void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException { file.getParentFile().mkdirs(); OutputStream os = null; InputStream is = null; @@ -116,36 +99,43 @@ public final class FileBackend { StreamUtil.close(is); } Logging.d(Config.LOGTAG, "output file name " + file); - return file; } - public static DownloadableFile compressImageAndCopyToPrivateStorage(Message message, Bitmap scaledBitmap) throws FileCopyException { + public static void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { + Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage"); + String mime = ConversationsPlusApplication.getInstance().getContentResolver().getType(uri); + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); + message.setRelativeFilePath(message.getUuid() + "." + extension); + copyFileToPrivateStorage(getFile(message), uri); + } + + public static DownloadableFile compressImageAndCopyToPrivateStorage(Message message, Bitmap scaledBitmap) throws FileCopyException { message.setRelativeFilePath(FileBackend.getPrivateImageDirectoryPath() + message.getUuid() + ".png"); - DownloadableFile file = getFile(message); - file.getParentFile().mkdirs(); - OutputStream os = null; - try { - file.createNewFile(); + DownloadableFile file = getFile(message); + file.getParentFile().mkdirs(); + OutputStream os = null; + try { + file.createNewFile(); os = new FileOutputStream(file); - boolean success = scaledBitmap.compress(Bitmap.CompressFormat.PNG, 75, os); - if (!success) { - throw new FileCopyException(R.string.error_compressing_image); - } - os.flush(); + boolean success = scaledBitmap.compress(Bitmap.CompressFormat.PNG, 75, os); + if (!success) { + throw new FileCopyException(R.string.error_compressing_image); + } + os.flush(); } catch (IOException e) { throw new FileCopyException(R.string.error_io_exception, e); - } catch (SecurityException e) { + } catch (SecurityException e) { throw new FileCopyException(R.string.error_security_exception_during_image_copy); } catch (NullPointerException e) { - throw new FileCopyException(R.string.error_io_exception); - } finally { - StreamUtil.close(os); - } + throw new FileCopyException(R.string.error_io_exception); + } finally { + StreamUtil.close(os); + } return file; - } + } - public static Uri getTakePhotoUri() { + public static Uri getTakePhotoUri() { StringBuilder pathBuilder = new StringBuilder(); pathBuilder.append(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)); pathBuilder.append('/'); @@ -170,4 +160,4 @@ public final class FileBackend { private FileBackend() { // Static helper class } -}
\ No newline at end of file +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/AbstractConnectionManager.java b/src/main/java/de/thedevstack/conversationsplus/services/AbstractConnectionManager.java index 6936c635..7c937475 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/AbstractConnectionManager.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/AbstractConnectionManager.java @@ -1,6 +1,37 @@ package de.thedevstack.conversationsplus.services; -import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.PowerManager; +import android.util.Log; +import android.util.Pair; + +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; + +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.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 de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.entities.DownloadableFile; public class AbstractConnectionManager { protected XmppConnectionService mXmppConnectionService; @@ -12,4 +43,91 @@ public class AbstractConnectionManager { public XmppConnectionService getXmppConnectionService() { return this.mXmppConnectionService; } + + public boolean hasStoragePermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return mXmppConnectionService.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } else { + return true; + } + } + + public static Pair<InputStream,Integer> createInputStream(DownloadableFile file, boolean gcm) throws FileNotFoundException { + FileInputStream is; + int size; + is = new FileInputStream(file); + size = (int) file.getSize(); + if (file.getKey() == null) { + return new Pair<InputStream,Integer>(is,size); + } + try { + if (gcm) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + InputStream cis = new org.bouncycastle.crypto.io.CipherInputStream(is, cipher); + return new Pair<>(cis, cipher.getOutputSize(size)); + } else { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted input stream"); + final int s = Config.REPORT_WRONG_FILESIZE_IN_OTR_JINGLE ? size : (size / 16 + 1) * 16; + return new Pair<InputStream,Integer>(new CipherInputStream(is, cipher),s); + } + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } + + public static OutputStream createAppendedOutputStream(DownloadableFile file) { + return createOutputStream(file, false, true); + } + + public static OutputStream createOutputStream(DownloadableFile file, boolean gcm) { + return createOutputStream(file, gcm, false); + } + + private static OutputStream createOutputStream(DownloadableFile file, boolean gcm, boolean append) { + FileOutputStream os; + try { + os = new FileOutputStream(file, append); + if (file.getKey() == null) { + return os; + } + } catch (FileNotFoundException e) { + return null; + } + try { + if (gcm) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + return new org.bouncycastle.crypto.io.CipherOutputStream(os, cipher); + } else { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted output stream"); + return new CipherOutputStream(os, cipher); + } + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } + + public PowerManager.WakeLock createWakeLock(String name) { + PowerManager powerManager = (PowerManager) mXmppConnectionService.getSystemService(Context.POWER_SERVICE); + return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,name); + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/AvatarService.java b/src/main/java/de/thedevstack/conversationsplus/services/AvatarService.java index 1cbda222..11ff36da 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/AvatarService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/AvatarService.java @@ -13,27 +13,28 @@ import java.util.List; import java.util.Locale; import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.utils.AvatarUtil; +import de.thedevstack.conversationsplus.utils.ImageUtil; +import de.thedevstack.conversationsplus.utils.UiUpdateHelper; +import de.thedevstack.conversationsplus.utils.XmppSendUtil; +import de.thedevstack.conversationsplus.xmpp.avatar.AvatarPacketGenerator; +import de.thedevstack.conversationsplus.xmpp.avatar.AvatarPacketParser; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Bookmark; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.ListItem; +import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.MucOptions; import de.thedevstack.conversationsplus.generator.IqGenerator; import de.thedevstack.conversationsplus.persistance.DatabaseBackend; import de.thedevstack.conversationsplus.ui.UiCallback; -import de.thedevstack.conversationsplus.utils.AvatarUtil; -import de.thedevstack.conversationsplus.utils.ImageUtil; import de.thedevstack.conversationsplus.utils.UIHelper; -import de.thedevstack.conversationsplus.utils.UiUpdateHelper; -import de.thedevstack.conversationsplus.utils.XmppSendUtil; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; -import de.thedevstack.conversationsplus.xmpp.avatar.AvatarPacketGenerator; -import de.thedevstack.conversationsplus.xmpp.avatar.AvatarPacketParser; import de.thedevstack.conversationsplus.xmpp.pep.Avatar; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; @@ -75,6 +76,36 @@ public class AvatarService { return avatar; } + public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) { + Contact c = user.getContact(); + if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null)) { + return get(c, size, cachedOnly); + } else { + return getImpl(user, size, cachedOnly); + } + } + + private Bitmap getImpl(final MucOptions.User user, final int size, boolean cachedOnly) { + final String KEY = key(user, size); + Bitmap avatar = ImageUtil.getBitmapFromCache(KEY); + if (avatar != null || cachedOnly) { + return avatar; + } + if (user.getAvatar() != null) { + avatar = AvatarUtil.getAvatar(user.getAvatar(), size); + } + if (avatar == null) { + Contact contact = user.getContact(); + if (contact != null) { + avatar = get(contact, size, cachedOnly); + } else { + avatar = get(user.getName(), size, cachedOnly); + } + } + ImageUtil.addBitmapToCache(KEY, avatar); + return avatar; + } + public void clear(Contact contact) { synchronized (this.sizes) { for (Integer size : sizes) { @@ -93,6 +124,16 @@ public class AvatarService { + contact.getJid() + "_" + String.valueOf(size); } + private String key(MucOptions.User user, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_CONTACT + "_" + user.getAccount().getJid().toBareJid() + "_" + + user.getFullJid() + "_" + String.valueOf(size); + } + public Bitmap get(ListItem item, int size) { return get(item,size,false); } @@ -138,7 +179,7 @@ public class AvatarService { if (bitmap != null || cachedOnly) { return bitmap; } - final List<MucOptions.User> users = new ArrayList<>(mucOptions.getUsers()); + final List<MucOptions.User> users = mucOptions.getUsers(); int count = users.size(); bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); @@ -146,11 +187,10 @@ public class AvatarService { if (count == 0) { String name = mucOptions.getConversation().getName(); - final String letter = name.isEmpty() ? "X" : name.substring(0,1); - final int color = UIHelper.getColorForName(name); - drawTile(canvas, letter, color, 0, 0, size, size); + drawTile(canvas, name, 0, 0, size, size); } else if (count == 1) { - drawTile(canvas, users.get(0), 0, 0, size, size); + drawTile(canvas, users.get(0), 0, 0, size / 2 - 1, size); + drawTile(canvas, mucOptions.getConversation().getAccount(), size / 2 + 1, 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); @@ -195,9 +235,13 @@ public class AvatarService { } public Bitmap get(Account account, int size) { + return get(account, size, false); + } + + public Bitmap get(Account account, int size, boolean cachedOnly) { final String KEY = key(account, size); Bitmap avatar = ImageUtil.getBitmapFromCache(KEY); - if (avatar != null) { + if (avatar != null || cachedOnly) { return avatar; } avatar = AvatarUtil.getAvatar(account.getAvatar(), size); @@ -208,6 +252,24 @@ public class AvatarService { return avatar; } + public Bitmap get(Message message, int size, boolean cachedOnly) { + final Conversation conversation = message.getConversation(); + if (message.getStatus() == Message.STATUS_RECEIVED) { + Contact c = message.getContact(); + if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null)) { + return get(c, size, cachedOnly); + } else if (message.getConversation().getMode() == Conversation.MODE_MULTI){ + MucOptions.User user = conversation.getMucOptions().findUser(message.getCounterpart().getResourcepart()); + if (user != null) { + return getImpl(user,size,cachedOnly); + } + } + return get(UIHelper.getMessageDisplayName(message), size, cachedOnly); + } else { + return get(conversation.getAccount(), size, cachedOnly); + } + } + public void clear(Account account) { synchronized (this.sizes) { for (Integer size : sizes) { @@ -216,6 +278,14 @@ public class AvatarService { } } + public void clear(MucOptions.User user) { + synchronized (this.sizes) { + for (Integer size : sizes) { + ImageUtil.removeBitmapFromCache(key(user, size)); + } + } + } + private String key(Account account, int size) { synchronized (this.sizes) { if (!this.sizes.contains(size)) { @@ -239,9 +309,7 @@ public class AvatarService { bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); final String trimmedName = name.trim(); - final String letter = trimmedName.isEmpty() ? "X" : trimmedName.substring(0,1); - final int color = UIHelper.getColorForName(name); - drawTile(canvas, letter, color, 0, 0, size, size); + drawTile(canvas, trimmedName, 0, 0, size, size); ImageUtil.addBitmapToCache(KEY, bitmap); return bitmap; } @@ -255,7 +323,7 @@ public class AvatarService { return PREFIX_GENERIC + "_" + name + "_" + String.valueOf(size); } - private void drawTile(Canvas canvas, String letter, int tileColor, + private boolean 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(); @@ -271,10 +339,11 @@ public class AvatarService { 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); + / 2 + rect.height() / 2, textPaint); + return true; } - private void drawTile(Canvas canvas, MucOptions.User user, int left, + private boolean drawTile(Canvas canvas, MucOptions.User user, int left, int top, int right, int bottom) { Contact contact = user.getContact(); if (contact != null) { @@ -284,24 +353,59 @@ public class AvatarService { } else if (contact.getAvatar() != null) { uri = AvatarUtil.getAvatarUri(contact.getAvatar()); } + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } else if (user.getAvatar() != null) { + Uri uri = AvatarUtil.getAvatarUri(user.getAvatar()); + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } + } + String name = contact != null ? contact.getDisplayName() : user.getName(); + drawTile(canvas, name, left, top, right, bottom); + return true; + } + + private boolean drawTile(Canvas canvas, Account account, int left, int top, int right, int bottom) { + String avatar = account.getAvatar(); + if (avatar != null) { + Uri uri = AvatarUtil.getAvatarUri(avatar); if (uri != null) { - Bitmap bitmap = ImageUtil.cropCenter(uri, bottom - top, right - left); - if (bitmap != null) { - drawTile(canvas, bitmap, left, top, right, bottom); - return; + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; } } } - String name = contact != null ? contact.getDisplayName() : user.getName(); - final String letter = name.isEmpty() ? "X" : name.substring(0,1); - final int color = UIHelper.getColorForName(name); - drawTile(canvas, letter, color, left, top, right, bottom); + return drawTile(canvas, account.getJid().toBareJid().toString(), left, top, right, bottom); + } + + private boolean drawTile(Canvas canvas, String name, int left, int top, int right, int bottom) { + if (name != null) { + final String letter = name.isEmpty() ? "X" : name.substring(0, 1); + final int color = UIHelper.getColorForName(name); + drawTile(canvas, letter, color, left, top, right, bottom); + return true; + } + return false; + } + + private boolean drawTile(Canvas canvas, Uri uri, int left, int top, int right, int bottom) { + if (uri != null) { + Bitmap bitmap = ImageUtil.cropCenter(uri, bottom - top, right - left); + if (bitmap != null) { + drawTile(canvas, bitmap, left, top, right, bottom); + return true; + } + } + return false; } - private void drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop, + private boolean 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); + return true; } public void publishAvatar(final Account account, diff --git a/src/main/java/de/thedevstack/conversationsplus/services/ContactChooserTargetService.java b/src/main/java/de/thedevstack/conversationsplus/services/ContactChooserTargetService.java new file mode 100644 index 00000000..8d48d686 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/ContactChooserTargetService.java @@ -0,0 +1,83 @@ +package de.thedevstack.conversationsplus.services; + +import android.annotation.TargetApi; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.graphics.drawable.Icon; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.service.chooser.ChooserTarget; +import android.service.chooser.ChooserTargetService; + +import java.util.ArrayList; +import java.util.List; + +import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.ui.ShareWithActivity; + +@TargetApi(Build.VERSION_CODES.M) +public class ContactChooserTargetService extends ChooserTargetService implements ServiceConnection { + + private final Object lock = new Object(); + + private XmppConnectionService mXmppConnectionService; + + private final int MAX_TARGETS = 5; + + @Override + public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) { + Intent intent = new Intent(this, XmppConnectionService.class); + intent.setAction("contact_chooser"); + startService(intent); + bindService(intent, this, Context.BIND_AUTO_CREATE); + ArrayList<ChooserTarget> chooserTargets = new ArrayList<>(); + try { + waitForService(); + final ArrayList<Conversation> conversations = new ArrayList<>(); + if (!mXmppConnectionService.areMessagesInitialized()) { + return chooserTargets; + } + mXmppConnectionService.populateWithOrderedConversations(conversations, false); + final ComponentName componentName = new ComponentName(this, ShareWithActivity.class); + final int pixel = (int) (48 * getResources().getDisplayMetrics().density); + for(int i = 0; i < Math.min(conversations.size(),MAX_TARGETS); ++i) { + final Conversation conversation = conversations.get(i); + final String name = conversation.getName(); + final Icon icon = Icon.createWithBitmap(AvatarService.getInstance().get(conversation, pixel)); + final float score = (1.0f / MAX_TARGETS) * i; + final Bundle extras = new Bundle(); + extras.putString("uuid", conversation.getUuid()); + chooserTargets.add(new ChooserTarget(name, icon, score, componentName, extras)); + } + } catch (InterruptedException e) { + } + unbindService(this); + return chooserTargets; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + XmppConnectionService.XmppConnectionBinder binder = (XmppConnectionService.XmppConnectionBinder) service; + mXmppConnectionService = binder.getService(); + synchronized (this.lock) { + lock.notifyAll(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mXmppConnectionService = null; + } + + private void waitForService() throws InterruptedException { + if (mXmppConnectionService == null) { + synchronized (this.lock) { + lock.wait(); + } + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/EventReceiver.java b/src/main/java/de/thedevstack/conversationsplus/services/EventReceiver.java index 4367dd1b..85e92552 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/EventReceiver.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/EventReceiver.java @@ -1,10 +1,11 @@ package de.thedevstack.conversationsplus.services; -import de.thedevstack.conversationsplus.persistance.DatabaseBackend; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import de.thedevstack.conversationsplus.persistance.DatabaseBackend; + public class EventReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { diff --git a/src/main/java/de/thedevstack/conversationsplus/services/ExportLogsService.java b/src/main/java/de/thedevstack/conversationsplus/services/ExportLogsService.java new file mode 100644 index 00000000..0b898125 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/ExportLogsService.java @@ -0,0 +1,147 @@ +package de.thedevstack.conversationsplus.services; + +import android.app.NotificationManager; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.persistance.DatabaseBackend; +import de.thedevstack.conversationsplus.persistance.FileBackend; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; + +public class ExportLogsService extends Service { + + private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + private static final String DIRECTORY_STRING_FORMAT = FileBackend.getConversationsFileDirectory() + "/logs/%s"; + private static final String MESSAGE_STRING_FORMAT = "(%s) %s: %s\n"; + private static final int NOTIFICATION_ID = 1; + private static AtomicBoolean running = new AtomicBoolean(false); + private DatabaseBackend mDatabaseBackend; + private List<Account> mAccounts; + + @Override + public void onCreate() { + mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext()); + mAccounts = mDatabaseBackend.getAccounts(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (running.compareAndSet(false, true)) { + new Thread(new Runnable() { + @Override + public void run() { + running.set(false); + export(); + stopForeground(true); + stopSelf(); + } + }).start(); + } + return START_NOT_STICKY; + } + + private void export() { + List<Conversation> conversations = mDatabaseBackend.getConversations(Conversation.STATUS_AVAILABLE); + conversations.addAll(mDatabaseBackend.getConversations(Conversation.STATUS_ARCHIVED)); + NotificationManager mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext()); + mBuilder.setContentTitle(getString(R.string.notification_export_logs_title)) + .setSmallIcon(R.drawable.ic_import_export_white_24dp) + .setProgress(conversations.size(), 0, false); + startForeground(NOTIFICATION_ID, mBuilder.build()); + + int progress = 0; + for (Conversation conversation : conversations) { + writeToFile(conversation); + progress++; + mBuilder.setProgress(conversations.size(), progress, false); + mNotifyManager.notify(NOTIFICATION_ID, mBuilder.build()); + } + } + + private void writeToFile(Conversation conversation) { + Jid accountJid = resolveAccountUuid(conversation.getAccountUuid()); + Jid contactJid = conversation.getJid(); + + File dir = new File(String.format(DIRECTORY_STRING_FORMAT,accountJid.toBareJid().toString())); + dir.mkdirs(); + + BufferedWriter bw = null; + try { + for (Message message : mDatabaseBackend.getMessagesIterable(conversation)) { + if (message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost()) { + String date = simpleDateFormat.format(new Date(message.getTimeSent())); + if (bw == null) { + bw = new BufferedWriter(new FileWriter( + new File(dir, contactJid.toBareJid().toString() + ".txt"))); + } + String jid = null; + switch (message.getStatus()) { + case Message.STATUS_RECEIVED: + jid = getMessageCounterpart(message); + break; + case Message.STATUS_SEND: + case Message.STATUS_SEND_RECEIVED: + case Message.STATUS_SEND_DISPLAYED: + jid = accountJid.toBareJid().toString(); + break; + } + if (jid != null) { + String body = message.hasFileOnRemoteHost() ? message.getFileParams().url.toString() : message.getBody(); + bw.write(String.format(MESSAGE_STRING_FORMAT, date, jid, + body.replace("\\\n", "\\ \n").replace("\n", "\\ \n"))); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (bw != null) { + bw.close(); + } + } catch (IOException e1) { + e1.printStackTrace(); + } + } + } + + private Jid resolveAccountUuid(String accountUuid) { + for (Account account : mAccounts) { + if (account.getUuid().equals(accountUuid)) { + return account.getJid(); + } + } + return null; + } + + private String getMessageCounterpart(Message message) { + String trueCounterpart = (String) message.getContentValues().get(Message.TRUE_COUNTERPART); + if (trueCounterpart != null) { + return trueCounterpart; + } else { + return message.getCounterpart().toString(); + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +}
\ No newline at end of file diff --git a/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java b/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java index 1e0abec1..a61ade40 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java @@ -1,5 +1,7 @@ package de.thedevstack.conversationsplus.services; +import android.util.Pair; + import java.math.BigInteger; import java.util.ArrayList; import java.util.HashSet; @@ -22,8 +24,8 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { private final XmppConnectionService mXmppConnectionService; - private final HashSet<Query> queries = new HashSet<Query>(); - private final ArrayList<Query> pendingQueries = new ArrayList<Query>(); + private final HashSet<Query> queries = new HashSet<>(); + private final ArrayList<Query> pendingQueries = new ArrayList<>(); public enum PagingOrder { NORMAL, @@ -34,7 +36,15 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { this.mXmppConnectionService = service; } - public void catchup(final Account account) { + private void catchup(final Account account) { + synchronized (this.queries) { + for(Iterator<Query> iterator = this.queries.iterator(); iterator.hasNext();) { + Query query = iterator.next(); + if (query.getAccount() == account) { + iterator.remove(); + } + } + } long startCatchup = getLastMessageTransmitted(account); long endCatchup = account.getXmppConnection().getLastSessionEstablished(); if (startCatchup == 0) { @@ -53,21 +63,33 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { this.execute(query); } - private long getLastMessageTransmitted(final Account account) { - long timestamp = 0; - for(final Conversation conversation : mXmppConnectionService.getConversations()) { - if (conversation.getAccount() == account) { - long tmp = conversation.getLastMessageTransmitted(); - if (tmp > timestamp) { - timestamp = tmp; - } - } + public void catchupMUC(final Conversation conversation) { + if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { + query(conversation, + 0, + System.currentTimeMillis()); + } else { + query(conversation, + conversation.getLastMessageTransmitted(), + System.currentTimeMillis()); } - return timestamp; + } + + private long getLastMessageTransmitted(final Account account) { + Pair<Long,String> pair = mXmppConnectionService.databaseBackend.getLastMessageReceived(account); + return pair == null ? 0 : pair.first; } public Query query(final Conversation conversation) { - return query(conversation,conversation.getAccount().getXmppConnection().getLastSessionEstablished()); + if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { + return query(conversation, + 0, + System.currentTimeMillis()); + } else { + return query(conversation, + conversation.getLastMessageTransmitted(), + conversation.getAccount().getXmppConnection().getLastSessionEstablished()); + } } public Query query(final Conversation conversation, long end) { @@ -80,6 +102,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { return null; } final Query query = new Query(conversation, start, end,PagingOrder.REVERSE); + query.reference = conversation.getFirstMamReference(); this.queries.add(query); this.execute(query); return query; @@ -110,9 +133,16 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.ERROR) { + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + synchronized (MessageArchiveService.this.queries) { + MessageArchiveService.this.queries.remove(query); + if (query.hasCallback()) { + query.callback(false); + } + } + } else if (packet.getType() != IqPacket.TYPE.RESULT) { Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": error executing mam: " + packet.toString()); - finalizeQuery(query); + finalizeQuery(query, true); } } }); @@ -123,32 +153,29 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } } - private void finalizeQuery(Query query) { + private void finalizeQuery(Query query, boolean done) { synchronized (this.queries) { this.queries.remove(query); } final Conversation conversation = query.getConversation(); if (conversation != null) { conversation.sort(); - if (conversation.setLastMessageTransmitted(query.getEnd())) { - this.mXmppConnectionService.databaseBackend.updateConversation(conversation); - } - if (query.hasCallback()) { - query.callback(); - } else { - conversation.setHasMessagesLeftOnServer(query.getMessageCount() > 0); - this.mXmppConnectionService.updateConversationUi(); - } + conversation.setHasMessagesLeftOnServer(!done); } else { for(Conversation tmp : this.mXmppConnectionService.getConversations()) { if (tmp.getAccount() == query.getAccount()) { tmp.sort(); - if (tmp.setLastMessageTransmitted(query.getEnd())) { - this.mXmppConnectionService.databaseBackend.updateConversation(tmp); - } } } } + if (query.hasCallback()) { + query.callback(done); + } else { + if (null != conversation) { + conversation.setHasMessagesLeftOnServer(query.getMessageCount() > 0); + } + this.mXmppConnectionService.updateConversationUi(); + } } public boolean queryInProgress(Conversation conversation, XmppConnectionService.OnMoreMessagesLoaded callback) { @@ -165,6 +192,10 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } } + public boolean queryInProgress(Conversation conversation) { + return queryInProgress(conversation, null); + } + public void processFin(Element fin, Jid from) { if (fin == null) { return; @@ -179,9 +210,16 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { Element first = set == null ? null : set.findChild("first"); Element relevant = query.getPagingOrder() == PagingOrder.NORMAL ? last : first; boolean abort = (query.getStart() == 0 && query.getTotalCount() >= Config.PAGE_SIZE) || query.getTotalCount() >= Config.MAM_MAX_MESSAGES; + if (query.getConversation() != null) { + query.getConversation().setFirstMamReference(first == null ? null : first.getContent()); + } if (complete || relevant == null || abort) { - this.finalizeQuery(query); - Logging.d(Config.LOGTAG,query.getAccount().getJid().toBareJid().toString()+": finished mam after "+query.getTotalCount()+" messages"); + final boolean done = (complete || query.getMessageCount() == 0) && query.getStart() == 0; + this.finalizeQuery(query, done); + Logging.d(Config.LOGTAG,query.getAccount().getJid().toBareJid()+": finished mam after "+query.getTotalCount()+" messages. messages left="+Boolean.toString(!done)); + if (query.getWith() == null && query.getMessageCount() > 0) { + mXmppConnectionService.getNotificationService().finishBacklog(true); + } } else { final Query nextQuery; if (query.getPagingOrder() == PagingOrder.NORMAL) { @@ -190,9 +228,8 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { nextQuery = query.prev(first == null ? null : first.getContent()); } this.execute(nextQuery); - this.finalizeQuery(query); + this.finalizeQuery(query, false); synchronized (this.queries) { - this.queries.remove(query); this.queries.add(nextQuery); } } @@ -298,10 +335,10 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { this.callback = callback; } - public void callback() { + public void callback(boolean done) { if (this.callback != null) { this.callback.onMoreMessagesLoaded(messageCount,conversation); - if (messageCount == 0) { + if (done) { this.callback.informUser(R.string.no_more_history_on_server); } } @@ -319,12 +356,9 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { return this.account; } - public void incrementTotalCount() { - this.totalCount++; - } - public void incrementMessageCount() { this.messageCount++; + this.totalCount++; } public int getTotalCount() { @@ -347,7 +381,8 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { public String toString() { StringBuilder builder = new StringBuilder(); if (this.muc()) { - builder.append("to="+this.getWith().toString()); + builder.append("to="); + builder.append(this.getWith().toString()); } else { builder.append("with="); if (this.getWith() == null) { diff --git a/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java b/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java index b02b4d82..92b7cfa1 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java @@ -1,6 +1,5 @@ package de.thedevstack.conversationsplus.services; -import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -9,7 +8,6 @@ import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; -import android.os.PowerManager; import android.os.SystemClock; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat.BigPictureStyle; @@ -27,12 +25,11 @@ import java.util.Calendar; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.utils.ImageUtil; +import de.thedevstack.conversationsplus.utils.MessageUtil; import de.tzur.conversations.Settings; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; @@ -67,9 +64,7 @@ public class NotificationService { && !message.isRead() && ConversationsPlusPreferences.showNotification() && !message.getConversation().isMuted() - && (message.getConversation().getMode() == Conversation.MODE_SINGLE - || ConversationsPlusPreferences.alwaysNotifyInConference() - || wasHighlightedOrPrivate(message) + && (message.getConversation().alwaysNotify() || wasHighlightedOrPrivate(message) ); } @@ -109,47 +104,47 @@ public class NotificationService { } } - @SuppressLint("NewApi") - @SuppressWarnings("deprecation") - private boolean isInteractive() { - final PowerManager pm = (PowerManager) mXmppConnectionService - .getSystemService(Context.POWER_SERVICE); + public void pushFromBacklog(final Message message) { + if (notify(message)) { + synchronized (notifications) { + pushToStack(message); + } + } + } - final boolean isScreenOn; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - isScreenOn = pm.isScreenOn(); - } else { - isScreenOn = pm.isInteractive(); + public void finishBacklog(boolean notify) { + synchronized (notifications) { + mXmppConnectionService.updateUnreadCountBadge(); + updateNotification(notify); } + } - return isScreenOn; + private void pushToStack(final Message message) { + final String conversationUuid = message.getConversationUuid(); + if (notifications.containsKey(conversationUuid)) { + notifications.get(conversationUuid).add(message); + } else { + final ArrayList<Message> mList = new ArrayList<>(); + mList.add(message); + notifications.put(conversationUuid, mList); + } } public void push(final Message message) { if (!notify(message)) { return; } - mXmppConnectionService.updateUnreadCountBadge(); - - final boolean isScreenOn = isInteractive(); - + mXmppConnectionService.updateUnreadCountBadge(); + final boolean isScreenOn = mXmppConnectionService.isInteractive(); if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) { return; } - synchronized (notifications) { - final String conversationUuid = message.getConversationUuid(); - if (notifications.containsKey(conversationUuid)) { - notifications.get(conversationUuid).add(message); - } else { - final ArrayList<Message> mList = new ArrayList<>(); - mList.add(message); - notifications.put(conversationUuid, mList); - } + pushToStack(message); final Account account = message.getConversation().getAccount(); final boolean doNotify = (!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn) - && !account.inGracePeriod() - && !this.inMiniGracePeriod(account); + && !account.inGracePeriod() + && !this.inMiniGracePeriod(account); updateNotification(doNotify); if (doNotify) { notifyPebble(message); @@ -172,10 +167,10 @@ public class NotificationService { } private void setNotificationColor(final Builder mBuilder) { - mBuilder.setColor(mXmppConnectionService.getResources().getColor(R.color.notification)); + mBuilder.setColor(mXmppConnectionService.getResources().getColor(R.color.primary)); } - private void updateNotification(final boolean notify) { + public void updateNotification(final boolean notify) { final NotificationManager notificationManager = (NotificationManager) ConversationsPlusApplication.getAppContext().getSystemService(Context.NOTIFICATION_SERVICE); final String ringtone = ConversationsPlusPreferences.notificationRingtone(); @@ -230,8 +225,13 @@ public class NotificationService { if (messages.size() > 0) { conversation = messages.get(0).getConversation(); final String name = conversation.getName(); - style.addLine(Html.fromHtml("<b>" + name + "</b> " - + UIHelper.getMessagePreview(mXmppConnectionService,messages.get(0)).first)); + if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) { + int count = messages.size(); + style.addLine(Html.fromHtml("<b>"+name+"</b>: "+mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages,count,count))); + } else { + style.addLine(Html.fromHtml("<b>" + name + "</b>: " + + UIHelper.getMessagePreview(mXmppConnectionService, messages.get(0)).first)); + } names.append(name); names.append(", "); } @@ -259,25 +259,32 @@ public class NotificationService { final Conversation conversation = messages.get(0).getConversation(); mBuilder.setLargeIcon(AvatarService.getInstance().get(conversation, getPixel(64))); mBuilder.setContentTitle(conversation.getName()); - Message message; - if ((message = getImage(messages)) != null) { - modifyForImage(mBuilder, message, messages, notify); + if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) { + int count = messages.size(); + mBuilder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages,count,count)); } else { - modifyForTextOnly(mBuilder, messages, notify); - } - if ((message = getFirstDownloadableMessage(messages)) != null) { - mBuilder.addAction( - Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? - R.drawable.ic_file_download_white_24dp : R.drawable.ic_action_download, - mXmppConnectionService.getResources().getString(R.string.download_x_file, - UIHelper.getFileDescriptionString(mXmppConnectionService, message)), - createDownloadIntent(message) - ); - } - if ((message = getFirstLocationMessage(messages)) != null) { - mBuilder.addAction(R.drawable.ic_room_white_24dp, - mXmppConnectionService.getString(R.string.show_location), - createShowLocationIntent(message)); + Message message; + if ((message = getImage(messages)) != null) { + modifyForImage(mBuilder, message, messages, notify); + } else if (conversation.getMode() == Conversation.MODE_MULTI) { + modifyForConference(mBuilder, conversation, messages, notify); + } else { + modifyForTextOnly(mBuilder, messages, notify); + } + if ((message = getFirstDownloadableMessage(messages)) != null) { + mBuilder.addAction( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? + R.drawable.ic_file_download_white_24dp : R.drawable.ic_action_download, + mXmppConnectionService.getResources().getString(R.string.download_x_file, + UIHelper.getFileDescriptionString(mXmppConnectionService, message)), + createDownloadIntent(message) + ); + } + if ((message = getFirstLocationMessage(messages)) != null) { + mBuilder.addAction(R.drawable.ic_room_white_24dp, + mXmppConnectionService.getString(R.string.show_location), + createShowLocationIntent(message)); + } } mBuilder.setContentIntent(createContentIntent(conversation)); } @@ -285,7 +292,7 @@ public class NotificationService { } private void modifyForImage(final Builder builder, final Message message, - final ArrayList<Message> messages, final boolean notify) { + final ArrayList<Message> messages, final boolean notify) { try { final Bitmap bitmap = ImageUtil.getThumbnail(message, getPixel(288), false); final ArrayList<Message> tmp = new ArrayList<>(); @@ -293,17 +300,17 @@ public class NotificationService { if (msg.getType() == Message.TYPE_TEXT && msg.getTransferable() == null) { tmp.add(msg); - } + } } final BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle(); bigPictureStyle.bigPicture(bitmap); if (tmp.size() > 0) { bigPictureStyle.setSummaryText(getMergedBodies(tmp)); - builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService,tmp.get(0)).first); + builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService, tmp.get(0)).first); } else { builder.setContentText(mXmppConnectionService.getString( R.string.received_x_file, - UIHelper.getFileDescriptionString(mXmppConnectionService,message))); + UIHelper.getFileDescriptionString(mXmppConnectionService, message))); } builder.setStyle(bigPictureStyle); } catch (final FileNotFoundException e) { @@ -312,19 +319,40 @@ public class NotificationService { } private void modifyForTextOnly(final Builder builder, - final ArrayList<Message> messages, final boolean notify) { + final ArrayList<Message> messages, final boolean notify) { builder.setStyle(new NotificationCompat.BigTextStyle().bigText(getMergedBodies(messages))); - builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService,messages.get(0)).first); + builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService, messages.get(0)).first); if (notify) { - builder.setTicker(UIHelper.getMessagePreview(mXmppConnectionService,messages.get(messages.size() - 1)).first); + builder.setTicker(UIHelper.getMessagePreview(mXmppConnectionService, messages.get(messages.size() - 1)).first); + } + } + + private void modifyForConference(Builder builder, Conversation conversation, List<Message> messages, boolean notify) { + final Message first = messages.get(0); + final Message last = messages.get(messages.size() - 1); + final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); + style.setBigContentTitle(conversation.getName()); + + for(Message message : messages) { + if (message.hasMeCommand()) { + style.addLine(UIHelper.getMessagePreview(mXmppConnectionService,message).first); + } else { + style.addLine(Html.fromHtml("<b>" + UIHelper.getMessageDisplayName(message) + "</b>: " + UIHelper.getMessagePreview(mXmppConnectionService, message).first)); + } + } + builder.setContentText((first.hasMeCommand() ? "" :UIHelper.getMessageDisplayName(first)+ ": ") +UIHelper.getMessagePreview(mXmppConnectionService, first).first); + builder.setStyle(style); + if (notify) { + builder.setTicker((last.hasMeCommand() ? "" : UIHelper.getMessageDisplayName(last) + ": ") + UIHelper.getMessagePreview(mXmppConnectionService,last).first); } } private Message getImage(final Iterable<Message> messages) { for (final Message message : messages) { - if (message.getType() == Message.TYPE_IMAGE + if (message.getType() != Message.TYPE_TEXT && message.getTransferable() == null - && message.getEncryption() != Message.ENCRYPTION_PGP) { + && message.getEncryption() != Message.ENCRYPTION_PGP + && message.getFileParams().height > 0) { return message; } } @@ -342,7 +370,7 @@ public class NotificationService { } private Message getFirstLocationMessage(final Iterable<Message> messages) { - for(final Message message : messages) { + for (final Message message : messages) { if (GeoHelper.isGeoUri(message.getBody())) { return message; } @@ -353,7 +381,7 @@ public class NotificationService { private CharSequence getMergedBodies(final ArrayList<Message> messages) { final StringBuilder text = new StringBuilder(); for (int i = 0; i < messages.size(); ++i) { - text.append(UIHelper.getMessagePreview(mXmppConnectionService,messages.get(i)).first); + text.append(UIHelper.getMessagePreview(mXmppConnectionService, messages.get(i)).first); if (i != messages.size() - 1) { text.append("\n"); } @@ -363,9 +391,9 @@ public class NotificationService { private PendingIntent createShowLocationIntent(final Message message) { Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(message); - for(Intent intent : intents) { + for (Intent intent : intents) { if (intent.resolveActivity(mXmppConnectionService.getPackageManager()) != null) { - return PendingIntent.getActivity(mXmppConnectionService,18,intent,PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getActivity(mXmppConnectionService, 18, intent, PendingIntent.FLAG_UPDATE_CURRENT); } } return createOpenConversationsIntent(); @@ -373,7 +401,7 @@ public class NotificationService { private PendingIntent createContentIntent(final String conversationUuid, final String downloadMessageUuid) { final TaskStackBuilder stackBuilder = TaskStackBuilder - .create(mXmppConnectionService); + .create(mXmppConnectionService); stackBuilder.addParentStack(ConversationActivity.class); final Intent viewConversationIntent = new Intent(mXmppConnectionService, @@ -425,30 +453,14 @@ public class NotificationService { } private PendingIntent createDisableAccountIntent(final Account account) { - final Intent intent = new Intent(mXmppConnectionService,XmppConnectionService.class); + final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class); intent.setAction(XmppConnectionService.ACTION_DISABLE_ACCOUNT); - intent.putExtra("account",account.getJid().toBareJid().toString()); - return PendingIntent.getService(mXmppConnectionService,0,intent,PendingIntent.FLAG_UPDATE_CURRENT); - } - - public boolean wasHighlightedOrPrivate(final Message message) { - final String nick = message.getConversation().getMucOptions().getActualNick(); - final Pattern highlight = generateNickHighlightPattern(nick); - if (message.getBody() == null || nick == null) { - return false; - } - final Matcher m = highlight.matcher(message.getBody()); - return (m.find() || message.getType() == Message.TYPE_PRIVATE); + intent.putExtra("account", account.getJid().toBareJid().toString()); + return PendingIntent.getService(mXmppConnectionService, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } - private static Pattern generateNickHighlightPattern(final 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" + Pattern.quote(nick) + "\\p{Punct}?\\b", - Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); + private boolean wasHighlightedOrPrivate(final Message message) { + return MessageUtil.wasHighlightedOrPrivate(message); } public void setOpenConversation(final Conversation conversation) { @@ -461,7 +473,7 @@ public class NotificationService { private int getPixel(final int dp) { final DisplayMetrics metrics = mXmppConnectionService.getResources() - .getDisplayMetrics(); + .getDisplayMetrics(); return ((int) (dp * metrics.density)); } @@ -471,7 +483,7 @@ public class NotificationService { private boolean inMiniGracePeriod(final Account account) { final int miniGrace = account.getStatus() == Account.State.ONLINE ? Config.MINI_GRACE_PERIOD - : Config.MINI_GRACE_PERIOD * 2; + : Config.MINI_GRACE_PERIOD * 2; return SystemClock.elapsedRealtime() < (this.mLastNotification + miniGrace); } @@ -479,41 +491,57 @@ public class NotificationService { final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service)); - mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_open_conversations)); + if (Config.SHOW_CONNECTED_ACCOUNTS) { + List<Account> accounts = mXmppConnectionService.getAccounts(); + int enabled = 0; + int connected = 0; + for (Account account : accounts) { + if (account.isOnlineAndConnected()) { + connected++; + enabled++; + } else if (!account.isOptionSet(Account.OPTION_DISABLED)) { + enabled++; + } + } + mBuilder.setContentText(mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled)); + } else { + mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_open_conversations)); + } mBuilder.setContentIntent(createOpenConversationsIntent()); mBuilder.setWhen(0); - mBuilder.setPriority(NotificationCompat.PRIORITY_MIN); + mBuilder.setPriority(Config.SHOW_CONNECTED_ACCOUNTS ? NotificationCompat.PRIORITY_DEFAULT : NotificationCompat.PRIORITY_MIN); final int cancelIcon; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mBuilder.setCategory(Notification.CATEGORY_SERVICE); - mBuilder.setSmallIcon(R.drawable.ic_import_export_white_24dp); cancelIcon = R.drawable.ic_cancel_white_24dp; } else { - mBuilder.setSmallIcon(R.drawable.ic_stat_communication_import_export); cancelIcon = R.drawable.ic_action_cancel; } + mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp); mBuilder.addAction(cancelIcon, mXmppConnectionService.getString(R.string.disable_foreground_service), createDisableForeground()); - setNotificationColor(mBuilder); return mBuilder.build(); } private PendingIntent createOpenConversationsIntent() { - return PendingIntent.getActivity(mXmppConnectionService, 0, new Intent(mXmppConnectionService,ConversationActivity.class),0); + return PendingIntent.getActivity(mXmppConnectionService, 0, new Intent(mXmppConnectionService, ConversationActivity.class), 0); } public void updateErrorNotification() { - final NotificationManager mNotificationManager = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE); + final NotificationManager notificationManager = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE); final List<Account> errors = new ArrayList<>(); for (final Account account : mXmppConnectionService.getAccounts()) { if (account.hasErrorStatus()) { errors.add(account); } } + if (mXmppConnectionService.getPreferences().getBoolean("keep_foreground_service", false)) { + notificationManager.notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification()); + } final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); if (errors.size() == 0) { - mNotificationManager.cancel(ERROR_NOTIFICATION_ID); + notificationManager.cancel(ERROR_NOTIFICATION_ID); return; } else if (errors.size() == 1) { mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.problem_connecting_to_account)); @@ -540,12 +568,12 @@ public class NotificationService { final TaskStackBuilder stackBuilder = TaskStackBuilder.create(mXmppConnectionService); stackBuilder.addParentStack(ConversationActivity.class); - final Intent manageAccountsIntent = new Intent(mXmppConnectionService,ManageAccountActivity.class); + final Intent manageAccountsIntent = new Intent(mXmppConnectionService, ManageAccountActivity.class); stackBuilder.addNextIntent(manageAccountsIntent); - final PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT); + final PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(resultPendingIntent); - mNotificationManager.notify(ERROR_NOTIFICATION_ID, mBuilder.build()); + notificationManager.notify(ERROR_NOTIFICATION_ID, mBuilder.build()); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java index bc982466..df5f72f5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java @@ -6,11 +6,16 @@ import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; import android.database.ContentObserver; +import android.graphics.Bitmap; +import android.media.AudioManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.FileObserver; import android.os.IBinder; @@ -18,7 +23,13 @@ import android.os.Looper; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; +import android.preference.PreferenceManager; import android.provider.ContactsContract; +import android.security.KeyChain; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.LruCache; +import android.util.Pair; import net.java.otr4j.OtrException; import net.java.otr4j.session.Session; @@ -26,24 +37,28 @@ import net.java.otr4j.session.SessionID; import net.java.otr4j.session.SessionImpl; import net.java.otr4j.session.SessionStatus; +import org.openintents.openpgp.IOpenPgpService2; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; import java.math.BigInteger; import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.Hashtable; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import de.duenndns.ssl.MemorizingTrustManager; - import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; @@ -57,16 +72,21 @@ import de.tzur.conversations.Settings; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.crypto.PgpEngine; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; +import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlMessage; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Blockable; import de.thedevstack.conversationsplus.entities.Bookmark; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.Conversation; -import de.thedevstack.conversationsplus.entities.Transferable; -import de.thedevstack.conversationsplus.entities.TransferablePlaceholder; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.MucOptions; import de.thedevstack.conversationsplus.entities.MucOptions.OnRenameListener; +import de.thedevstack.conversationsplus.entities.Presence; +import de.thedevstack.conversationsplus.entities.Roster; +import de.thedevstack.conversationsplus.entities.ServiceDiscoveryResult; +import de.thedevstack.conversationsplus.entities.Transferable; +import de.thedevstack.conversationsplus.entities.TransferablePlaceholder; import de.thedevstack.conversationsplus.generator.IqGenerator; import de.thedevstack.conversationsplus.generator.MessageGenerator; import de.thedevstack.conversationsplus.generator.PresenceGenerator; @@ -87,6 +107,7 @@ import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.OnBindListener; import de.thedevstack.conversationsplus.xmpp.OnContactStatusChanged; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; +import de.thedevstack.conversationsplus.xmpp.OnKeyStatusUpdated; import de.thedevstack.conversationsplus.xmpp.OnMessageAcknowledged; import de.thedevstack.conversationsplus.xmpp.OnMessagePacketReceived; import de.thedevstack.conversationsplus.xmpp.OnPresencePacketReceived; @@ -110,9 +131,16 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public static final String ACTION_CLEAR_NOTIFICATION = "clear_notification"; public static final String ACTION_DISABLE_FOREGROUND = "disable_foreground"; - private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; public static final String ACTION_TRY_AGAIN = "try_again"; public static final String ACTION_DISABLE_ACCOUNT = "disable_account"; + private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; + public static final String ACTION_GCM_TOKEN_REFRESH = "gcm_token_refresh"; + public static final String ACTION_GCM_MESSAGE_RECEIVED = "gcm_message_received"; + private final IBinder mBinder = new XmppConnectionBinder(); + private final List<Conversation> conversations = new CopyOnWriteArrayList<>(); + private final IqGenerator mIqGenerator = new IqGenerator(); + private final List<String> mInProgressAvatarFetches = new ArrayList<>(); + public DatabaseBackend databaseBackend; private ContentObserver contactObserver = new ContentObserver(null) { @Override public void onChange(boolean selfChange) { @@ -123,59 +151,29 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa startService(intent); } }; - - private final IBinder mBinder = new XmppConnectionBinder(); - private final List<Conversation> conversations = new CopyOnWriteArrayList<>(); - private final FileObserver fileObserver = new FileObserver( - FileBackend.getConversationsImageDirectory()) { - - @Override - public void onEvent(int event, String path) { - if (event == FileObserver.DELETE) { - markFileDeleted(path.split("\\.")[0]); - } - } - }; - private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() { - - @Override - public void onJinglePacketReceived(Account account, JinglePacket packet) { - mJingleConnectionManager.deliverPacket(account, packet); - } - }; - private final OnBindListener mOnBindListener = new OnBindListener() { - - @Override - public void onBind(final Account account) { - account.getRoster().clearPresences(); - account.pendingConferenceJoins.clear(); - account.pendingConferenceLeaves.clear(); - fetchRosterFromServer(account); - fetchBookmarks(account); - sendPresence(account); - connectMultiModeConversations(account); - updateConversationUi(); - } - }; - private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { - + private MemorizingTrustManager mMemorizingTrustManager; + private NotificationService mNotificationService = new NotificationService( + this); + private OnMessagePacketReceived mMessageParser = new MessageParser(this); + private OnPresencePacketReceived mPresenceParser = new PresenceParser(this); + private IqParser mIqParser = new IqParser(this); + private OnIqPacketReceived mDefaultIqHandler = new OnIqPacketReceived() { @Override - public void onMessageAcknowledged(Account account, String uuid) { - for (final Conversation conversation : getConversations()) { - if (conversation.getAccount() == account) { - Message message = conversation.findUnsentMessageWithUuid(uuid); - if (message != null) { - markMessage(message, Message.STATUS_SEND); - if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) { - databaseBackend.updateConversation(conversation); - } - } + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() != IqPacket.TYPE.RESULT) { + Element error = packet.findChild("error"); + String text = error != null ? error.findChildContent("text") : null; + if (text != null) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": received iq error - " + text); } } } }; - private final IqGenerator mIqGenerator = new IqGenerator(); - public DatabaseBackend databaseBackend; + private MessageGenerator mMessageGenerator = new MessageGenerator(); + private PresenceGenerator mPresenceGenerator = new PresenceGenerator(); + private List<Account> accounts; + private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager( + this); public OnContactStatusChanged onContactStatusChanged = new OnContactStatusChanged() { @Override @@ -202,84 +200,128 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } }; - private MemorizingTrustManager mMemorizingTrustManager; - private NotificationService mNotificationService = new NotificationService( + private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( this); - private OnMessagePacketReceived mMessageParser = new MessageParser(this); - private OnPresencePacketReceived mPresenceParser = new PresenceParser(this); - private IqParser mIqParser = new IqParser(this); - private OnIqPacketReceived mDefaultIqHandler = new OnIqPacketReceived() { + private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); + private PushManagementService mPushManagementService = new PushManagementService(this); + private OnConversationUpdate mOnConversationUpdate = null; + private final FileObserver fileObserver = new FileObserver( + FileBackend.getConversationsImageDirectory()) { + @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.ERROR) { - Element error = packet.findChild("error"); - String text = error != null ? error.findChildContent("text") : null; - if (text != null) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": received iq error - "+text); + public void onEvent(int event, String path) { + if (event == FileObserver.DELETE) { + markFileDeleted(path.split("\\.")[0]); + } + } + }; + private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() { + + @Override + public void onJinglePacketReceived(Account account, JinglePacket packet) { + mJingleConnectionManager.deliverPacket(account, packet); + } + }; + private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { + + @Override + public void onMessageAcknowledged(Account account, String uuid) { + for (final Conversation conversation : getConversations()) { + if (conversation.getAccount() == account) { + Message message = conversation.findUnsentMessageWithUuid(uuid); + if (message != null) { + markMessage(message, Message.STATUS_SEND); + } } } } }; - private MessageGenerator mMessageGenerator = new MessageGenerator(); - private PresenceGenerator mPresenceGenerator = new PresenceGenerator(); - private List<Account> accounts; - private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager( - this); - private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( - this); - private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); - private OnConversationUpdate mOnConversationUpdate = null; private int convChangedListenerCount = 0; private OnShowErrorToast mOnShowErrorToast = null; private int showErrorToastListenerCount = 0; private int unreadCount = -1; private OnAccountUpdate mOnAccountUpdate = null; + private OnCaptchaRequested mOnCaptchaRequested = null; + private int accountChangedListenerCount = 0; + private int captchaRequestedListenerCount = 0; + private OnRosterUpdate mOnRosterUpdate = null; + private OnUpdateBlocklist mOnUpdateBlocklist = null; + private int updateBlocklistListenerCount = 0; + private int rosterChangedListenerCount = 0; + private OnMucRosterUpdate mOnMucRosterUpdate = null; + private int mucRosterChangedListenerCount = 0; + private OnKeyStatusUpdated mOnKeyStatusUpdated = null; + private int keyStatusUpdatedListenerCount = 0; + private SecureRandom mRandom; + private LruCache<Pair<String,String>,ServiceDiscoveryResult> discoCache = new LruCache<>(20); + private final OnBindListener mOnBindListener = new OnBindListener() { + + @Override + public void onBind(final Account account) { + account.getRoster().clearPresences(); + mJingleConnectionManager.cancelInTransmission(); + fetchRosterFromServer(account); + fetchBookmarks(account); + sendPresence(account); + if (mPushManagementService.available(account)) { + mPushManagementService.registerPushTokenOnServer(account); + } + connectMultiModeConversations(account); + syncDirtyContacts(account); + } + }; private OnStatusChanged statusListener = new OnStatusChanged() { @Override - public void onStatusChanged(Account account) { + public void onStatusChanged(final Account account) { XmppConnection connection = account.getXmppConnection(); if (mOnAccountUpdate != null) { mOnAccountUpdate.onAccountUpdate(); } if (account.getStatus() == Account.State.ONLINE) { - for (Conversation conversation : account.pendingConferenceLeaves) { - leaveMuc(conversation); - } - for (Conversation conversation : account.pendingConferenceJoins) { - joinMuc(conversation); - } mMessageArchiveService.executePendingQueries(account); - mJingleConnectionManager.cancelInTransmission(); - List<Conversation> conversations = getConversations(); - for (Conversation conversation : conversations) { - if (conversation.getAccount() == account) { - conversation.startOtrIfNeeded(); - sendUnsentMessages(conversation); - } - } if (connection != null && connection.getFeatures().csi()) { if (checkListeners()) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid() - + " sending csi//inactive"); + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//inactive"); connection.sendInactive(); } else { - Logging.d(Config.LOGTAG, account.getJid().toBareJid() - + " sending csi//active"); + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//active"); connection.sendActive(); } } - syncDirtyContacts(account); - scheduleWakeUpCall(Config.PING_MAX_INTERVAL,account.getUuid().hashCode()); + List<Conversation> conversations = getConversations(); + for (Conversation conversation : conversations) { + if (conversation.getAccount() == account + && !account.pendingConferenceJoins.contains(conversation)) { + if (!conversation.startOtrIfNeeded()) { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": couldn't start OTR with "+conversation.getContact().getJid()+" when needed"); + } + sendUnsentMessages(conversation); + } + } + for (Conversation conversation : account.pendingConferenceLeaves) { + leaveMuc(conversation); + } + account.pendingConferenceLeaves.clear(); + for (Conversation conversation : account.pendingConferenceJoins) { + joinMuc(conversation); + } + account.pendingConferenceJoins.clear(); + scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode()); } else if (account.getStatus() == Account.State.OFFLINE) { resetSendingToWaiting(account); - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - int timeToReconnect = mRandom.nextInt(50) + 10; - scheduleWakeUpCall(timeToReconnect,account.getUuid().hashCode()); + final boolean disabled = account.isOptionSet(Account.OPTION_DISABLED); + final boolean pushMode = Config.CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND + && mPushManagementService.available(account) + && checkListeners(); + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": push mode "+Boolean.toString(pushMode)); + if (!disabled && !pushMode) { + int timeToReconnect = mRandom.nextInt(20) + 10; + scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode()); } } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) { databaseBackend.updateAccount(account); - reconnectAccount(account, true); + reconnectAccount(account, true, false); } else if ((account.getStatus() != Account.State.CONNECTING) && (account.getStatus() != Account.State.NO_INTERNET)) { if (connection != null) { @@ -288,53 +330,49 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa + ": error connecting account. try again in " + next + "s for the " + (connection.getAttempt() + 1) + " time"); - scheduleWakeUpCall(next,account.getUuid().hashCode()); + scheduleWakeUpCall(next, account.getUuid().hashCode()); } - } + } getNotificationService().updateErrorNotification(); } }; - private int accountChangedListenerCount = 0; - private OnRosterUpdate mOnRosterUpdate = null; - private OnUpdateBlocklist mOnUpdateBlocklist = null; - private int updateBlocklistListenerCount = 0; - private int rosterChangedListenerCount = 0; - private OnMucRosterUpdate mOnMucRosterUpdate = null; - private int mucRosterChangedListenerCount = 0; - private SecureRandom mRandom; private OpenPgpServiceConnection pgpServiceConnection; private PgpEngine mPgpEngine = null; private WakeLock wakeLock; private PowerManager pm; + private LruCache<String, Bitmap> mBitmapCache; private Thread mPhoneContactMergerThread; + private EventReceiver mEventReceiver = new EventReceiver(); private boolean mRestoredFromDatabase = false; + public boolean areMessagesInitialized() { return this.mRestoredFromDatabase; } public PgpEngine getPgpEngine() { - if (pgpServiceConnection.isBound()) { + if (!Config.supportOpenPgp()) { + return null; + } else if (pgpServiceConnection != null && pgpServiceConnection.isBound()) { if (this.mPgpEngine == null) { this.mPgpEngine = new PgpEngine(new OpenPgpApi( - getApplicationContext(), - pgpServiceConnection.getService()), this); + getApplicationContext(), + pgpServiceConnection.getService()), this); } return mPgpEngine; } else { return null; } - } public void attachLocationToConversation(final Conversation conversation, final Uri uri, final UiCallback<Message> callback) { - int encryption = conversation.getNextEncryption(ConversationsPlusPreferences.forceEncryption()); + int encryption = conversation.getNextEncryption(); if (encryption == Message.ENCRYPTION_PGP) { encryption = Message.ENCRYPTION_DECRYPTED; } - Message message = new Message(conversation,uri.toString(),encryption); + Message message = new Message(conversation, uri.toString(), encryption); if (conversation.getNextCounterpart() != null) { message.setCounterpart(conversation.getNextCounterpart()); } @@ -346,16 +384,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void attachFileToConversation(final Conversation conversation, - final Uri uri, - final UiCallback<Message> callback) { + final Uri uri, + final UiCallback<Message> callback) { final Message message; - boolean forceEncryption = ConversationsPlusPreferences.forceEncryption(); - if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) { - message = new Message(conversation, "", - Message.ENCRYPTION_DECRYPTED); + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); } else { - message = new Message(conversation, "", - conversation.getNextEncryption(forceEncryption)); + message = new Message(conversation, "", conversation.getNextEncryption()); } message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_FILE); @@ -388,7 +423,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - public Conversation find(Bookmark bookmark) { + public Conversation find(Bookmark bookmark) { return find(bookmark.getAccount(), bookmark.getJid()); } @@ -399,6 +434,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public int onStartCommand(Intent intent, int flags, int startId) { final String action = intent == null ? null : intent.getAction(); + boolean interactive = false; if (action != null) { switch (action) { case ConnectivityManager.CONNECTIVITY_ACTION: @@ -408,9 +444,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa break; case ACTION_MERGE_PHONE_CONTACTS: if (mRestoredFromDatabase) { - PhoneHelper.loadPhoneContacts(getApplicationContext(), - new CopyOnWriteArrayList<Bundle>(), - this); + loadPhoneContacts(); } return START_STICKY; case Intent.ACTION_SHUTDOWN: @@ -425,19 +459,36 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa break; case ACTION_TRY_AGAIN: resetAllAttemptCounts(false); + interactive = true; break; case ACTION_DISABLE_ACCOUNT: try { String jid = intent.getStringExtra("account"); Account account = jid == null ? null : findAccountByJid(Jid.fromString(jid)); if (account != null) { - account.setOption(Account.OPTION_DISABLED,true); + account.setOption(Account.OPTION_DISABLED, true); updateAccount(account); } } catch (final InvalidJidException ignored) { break; } break; + case AudioManager.RINGER_MODE_CHANGED_ACTION: + if (xaOnSilentMode()) { + refreshAllPresences(); + } + break; + case Intent.ACTION_SCREEN_OFF: + case Intent.ACTION_SCREEN_ON: + if (awayWhenScreenOff()) { + refreshAllPresences(); + } + break; + case ACTION_GCM_TOKEN_REFRESH: + refreshAllGcmTokens(); + break; + case ACTION_GCM_MESSAGE_RECEIVED: + Log.d(Config.LOGTAG,"gcm push message arrived in service. extras="+intent.getExtras()); } } this.wakeLock.acquire(); @@ -460,36 +511,42 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa long lastReceived = account.getXmppConnection().getLastPacketReceived(); long lastSent = account.getXmppConnection().getLastPingSent(); long pingInterval = "ui".equals(action) ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000; - long msToNextPing = (Math.max(lastReceived,lastSent) + pingInterval) - SystemClock.elapsedRealtime(); + long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime(); long pingTimeoutIn = (lastSent + Config.PING_TIMEOUT * 1000) - SystemClock.elapsedRealtime(); if (lastSent > lastReceived) { if (pingTimeoutIn < 0) { Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout"); - this.reconnectAccount(account, true); + this.reconnectAccount(account, true, interactive); } else { int secs = (int) (pingTimeoutIn / 1000); - this.scheduleWakeUpCall(secs,account.getUuid().hashCode()); + this.scheduleWakeUpCall(secs, account.getUuid().hashCode()); } } else if (msToNextPing <= 0) { account.getXmppConnection().sendPing(); - Logging.d(Config.LOGTAG, account.getJid().toBareJid()+" send ping"); - this.scheduleWakeUpCall(Config.PING_TIMEOUT,account.getUuid().hashCode()); + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping"); + this.scheduleWakeUpCall(Config.PING_TIMEOUT, account.getUuid().hashCode()); } else { this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode()); } } else if (account.getStatus() == Account.State.OFFLINE) { - reconnectAccount(account,true); + reconnectAccount(account, true, interactive); } else if (account.getStatus() == Account.State.CONNECTING) { - long timeout = Config.CONNECT_TIMEOUT - ((SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000); + long secondsSinceLastConnect = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000; + long secondsSinceLastDisco = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastDiscoStarted()) / 1000; + long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco; + long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect; if (timeout < 0) { Logging.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting"); - reconnectAccount(account, true); + reconnectAccount(account, true, interactive); + } else if (discoTimeout < 0) { + account.getXmppConnection().sendDiscoTimeout(); + scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode()); } else { - scheduleWakeUpCall((int) timeout,account.getUuid().hashCode()); + scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode()); } } else { if (account.getXmppConnection().getTimeToNextAttempt() <= 0) { - reconnectAccount(account, true); + reconnectAccount(account, true, interactive); } } @@ -499,10 +556,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } } - /*PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE); - if (!pm.isScreenOn()) { - removeStaleListeners(); - }*/ if (wakeLock.isHeld()) { try { wakeLock.release(); @@ -512,9 +565,46 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return START_STICKY; } + private boolean xaOnSilentMode() { + return getPreferences().getBoolean("xa_on_silent_mode", false); + } + + private boolean awayWhenScreenOff() { + return getPreferences().getBoolean("away_when_screen_off", false); + } + + private Presence.Status getTargetPresence() { + if (xaOnSilentMode() && isPhoneSilenced()) { + return Presence.Status.XA; + } else if (awayWhenScreenOff() && !isInteractive()) { + return Presence.Status.AWAY; + } else { + return Presence.Status.ONLINE; + } + } + + @SuppressLint("NewApi") + @SuppressWarnings("deprecation") + public boolean isInteractive() { + final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + + final boolean isScreenOn; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + isScreenOn = pm.isScreenOn(); + } else { + isScreenOn = pm.isInteractive(); + } + return isScreenOn; + } + + private boolean isPhoneSilenced() { + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + return audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT; + } + private void resetAllAttemptCounts(boolean reallyAll) { Logging.d(Config.LOGTAG, "resetting all attepmt counts"); - for(Account account : accounts) { + for (Account account : accounts) { if (account.hasErrorStatus() || reallyAll) { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { @@ -526,7 +616,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public boolean hasInternetConnection() { ConnectivityManager cm = (ConnectivityManager) getApplicationContext() - .getSystemService(Context.CONNECTIVITY_SERVICE); + .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); return activeNetwork != null && activeNetwork.isConnected(); } @@ -557,25 +647,80 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa PRNGFixes.apply(); this.mRandom = new SecureRandom(); updateMemorizingTrustmanager(); + final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); + final int cacheSize = maxMemory / 8; + this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) { + @Override + protected int sizeOf(final String key, final Bitmap bitmap) { + return bitmap.getByteCount() / 1024; + } + }; this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext()); this.accounts = databaseBackend.getAccounts(); - for (final Account account : this.accounts) { - account.initAccountServices(this); - } restoreFromDatabase(); getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver); this.fileObserver.startWatching(); - this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain"); - this.pgpServiceConnection.bindToService(); + + if (Config.supportOpenPgp()) { + this.pgpServiceConnection = new OpenPgpServiceConnection(getApplicationContext(), "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() { + @Override + public void onBound(IOpenPgpService2 service) { + for (Account account : accounts) { + if (account.getPgpDecryptionService() != null) { + account.getPgpDecryptionService().onOpenPgpServiceBound(); + } + } + } + + @Override + public void onError(Exception e) { + } + }); + this.pgpServiceConnection.bindToService(); + } this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"XmppConnectionService"); + this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XmppConnectionService"); toggleForegroundService(); updateUnreadCountBadge(); UiUpdateHelper.initXmppConnectionService(this); + toggleScreenEventReceiver(); + } + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + if (level >= TRIM_MEMORY_COMPLETE) { + Log.d(Config.LOGTAG, "clear cache due to low memory"); + getBitmapCache().evictAll(); + } + } + + @Override + public void onDestroy() { + try { + unregisterReceiver(this.mEventReceiver); + } catch (IllegalArgumentException e) { + //ignored + } + super.onDestroy(); + } + + public void toggleScreenEventReceiver() { + if (awayWhenScreenOff()) { + final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + registerReceiver(this.mEventReceiver, filter); + } else { + try { + unregisterReceiver(this.mEventReceiver); + } catch (IllegalArgumentException e) { + //ignored + } + } } public void toggleForegroundService() { @@ -598,19 +743,27 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa for (final Account account : accounts) { databaseBackend.writeRoster(account.getRoster()); if (account.getXmppConnection() != null) { - disconnect(account, false); + new Thread(new Runnable() { + @Override + public void run() { + disconnect(account, false); + } + }).start(); + cancelWakeUpCall(account.getUuid().hashCode()); } } - Context context = getApplicationContext(); - AlarmManager alarmManager = (AlarmManager) context - .getSystemService(Context.ALARM_SERVICE); - Intent intent = new Intent(context, EventReceiver.class); - alarmManager.cancel(PendingIntent.getBroadcast(context, 0, intent, 0)); Logging.d(Config.LOGTAG, "good bye"); stopSelf(); } - protected void scheduleWakeUpCall(int seconds, int requestCode) { + private void cancelWakeUpCall(int requestCode) { + final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + final Intent intent = new Intent(this, EventReceiver.class); + intent.setAction("ping"); + alarmManager.cancel(PendingIntent.getBroadcast(this, requestCode, intent, 0)); + } + + public void scheduleWakeUpCall(int seconds, int requestCode) { final long timeToWake = SystemClock.elapsedRealtime() + (seconds < 0 ? 1 : seconds + 1) * 1000; Context context = getApplicationContext(); @@ -633,6 +786,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa connection.setOnBindListener(this.mOnBindListener); connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService); + AxolotlService axolotlService = account.getAxolotlService(); + if (axolotlService != null) { + connection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService); + } return connection; } @@ -643,37 +800,41 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - private void sendFileMessage(final Message message) { + private void sendFileMessage(final Message message, final boolean delay) { Logging.d(Config.LOGTAG, "send file message"); final Account account = message.getConversation().getAccount(); final XmppConnection connection = account.getXmppConnection(); if (connection != null && connection.getFeatures().httpUpload()) { - mHttpConnectionManager.createNewUploadConnection(message); + mHttpConnectionManager.createNewUploadConnection(message, delay); } else { mJingleConnectionManager.createNewConnection(message); } } public void sendMessage(final Message message) { - sendMessage(message, false); + sendMessage(message, false, false); } - private void sendMessage(final Message message, final boolean resend) { + private void sendMessage(final Message message, final boolean resend, final boolean delay) { final Account account = message.getConversation().getAccount(); final Conversation conversation = message.getConversation(); account.deactivateGracePeriod(); MessagePacket packet = null; - boolean saveInDb = true; + final boolean addToConversation = (conversation.getMode() != Conversation.MODE_MULTI + || account.getServerIdentity() != XmppConnection.Identity.SLACK) + && !message.edited(); + boolean saveInDb = addToConversation; message.setStatus(Message.STATUS_WAITING); if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) { message.getConversation().endOtrIfNeeded(); - message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() { - @Override - public void onMessageFound(Message message) { - markMessage(message,Message.STATUS_SEND_FAILED); - } - }); + message.getConversation().findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, + new Conversation.OnMessageFound() { + @Override + public void onMessageFound(Message message) { + markMessage(message, Message.STATUS_SEND_FAILED); + } + }); } if (account.isOnlineAndConnected()) { @@ -681,24 +842,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa case Message.ENCRYPTION_NONE: if (message.needsUploading()) { if (account.httpUploadAvailable() || message.fixCounterpart()) { - this.sendFileMessage(message); + this.sendFileMessage(message, delay); } else { break; } } else { - packet = mMessageGenerator.generateChat(message,resend); + packet = mMessageGenerator.generateChat(message); } break; case Message.ENCRYPTION_PGP: case Message.ENCRYPTION_DECRYPTED: if (message.needsUploading()) { if (account.httpUploadAvailable() || message.fixCounterpart()) { - this.sendFileMessage(message); + this.sendFileMessage(message, delay); } else { break; } } else { - packet = mMessageGenerator.generatePgpChat(message,resend); + packet = mMessageGenerator.generatePgpChat(message); } break; case Message.ENCRYPTION_OTR: @@ -712,16 +873,37 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (message.needsUploading()) { mJingleConnectionManager.createNewConnection(message); } else { - packet = mMessageGenerator.generateOtrChat(message,resend); + packet = mMessageGenerator.generateOtrChat(message); } } else if (otrSession == null) { if (message.fixCounterpart()) { conversation.startOtrSession(message.getCounterpart().getResourcepart(), true); } else { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not fix counterpart for OTR message to contact "+message.getContact().getJid()); break; } + } else { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+" OTR session with "+message.getContact()+" is in wrong state: "+otrSession.getSessionStatus().toString()); + } + break; + case Message.ENCRYPTION_AXOLOTL: + message.setAxolotlFingerprint(account.getAxolotlService().getOwnFingerprint()); + if (message.needsUploading()) { + if (account.httpUploadAvailable() || message.fixCounterpart()) { + this.sendFileMessage(message, delay); + } else { + break; + } + } else { + XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message); + if (axolotlMessage == null) { + account.getAxolotlService().preparePayloadMessage(message, delay); + } else { + packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage); + } } break; + } if (packet != null) { if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) { @@ -731,7 +913,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } } else { - switch(message.getEncryption()) { + switch (message.getEncryption()) { case Message.ENCRYPTION_DECRYPTED: if (!message.needsUploading()) { String pgpBody = message.getEncryptedBody(); @@ -746,28 +928,41 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa break; case Message.ENCRYPTION_OTR: if (!conversation.hasValidOtrSession() && message.getCounterpart() != null) { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": create otr session without starting for "+message.getContact().getJid()); conversation.startOtrSession(message.getCounterpart().getResourcepart(), false); } break; + case Message.ENCRYPTION_AXOLOTL: + message.setAxolotlFingerprint(account.getAxolotlService().getOwnFingerprint()); + break; } } if (resend) { - if (packet != null) { + if (packet != null && addToConversation) { if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) { - markMessage(message,Message.STATUS_UNSEND); + markMessage(message, Message.STATUS_UNSEND); } else { - markMessage(message,Message.STATUS_SEND); + markMessage(message, Message.STATUS_SEND); } } } else { - conversation.add(message); - if (saveInDb && (message.getEncryption() == Message.ENCRYPTION_NONE || !ConversationsPlusPreferences.dontSaveEncrypted())) { - databaseBackend.createMessage(message); + if (addToConversation) { + conversation.add(message); + } + if (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages()) { + if (saveInDb) { + databaseBackend.createMessage(message); + } else if (message.edited()) { + databaseBackend.updateMessage(message, message.getEditedId()); + } } updateConversationUi(); } if (packet != null) { + if (delay) { + mMessageGenerator.addDelay(packet, message.getTimeSent()); + } if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { if (ConversationsPlusPreferences.chatStates()) { packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); @@ -782,13 +977,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onMessageFound(Message message) { - resendMessage(message); + resendMessage(message, true); } }); } - public void resendMessage(final Message message) { - sendMessage(message, true); + public void resendMessage(final Message message, final boolean delay) { + sendMessage(message, true, delay); } public void fetchRosterFromServer(final Account account) { @@ -811,34 +1006,42 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - final Element query = packet.query(); - final List<Bookmark> bookmarks = new CopyOnWriteArrayList<>(); - final Element storage = query.findChild("storage", - "storage:bookmarks"); - if (storage != null) { - for (final Element item : storage.getChildren()) { - if (item.getName().equals("conference")) { - final Bookmark bookmark = Bookmark.parse(item, account); - bookmarks.add(bookmark); - Conversation conversation = find(bookmark); - if (conversation != null) { - conversation.setBookmark(bookmark); - } else if (bookmark.autojoin() && bookmark.getJid() != null) { - conversation = findOrCreateConversation( - account, bookmark.getJid(), true); - conversation.setBookmark(bookmark); - joinMuc(conversation); + if (packet.getType() == IqPacket.TYPE.RESULT) { + final Element query = packet.query(); + final HashMap<Jid, Bookmark> bookmarks = new HashMap<>(); + final Element storage = query.findChild("storage", "storage:bookmarks"); + final boolean autojoin = respectAutojoin(); + if (storage != null) { + for (final Element item : storage.getChildren()) { + if (item.getName().equals("conference")) { + final Bookmark bookmark = Bookmark.parse(item, account); + Bookmark old = bookmarks.put(bookmark.getJid(), bookmark); + if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) { + bookmark.setBookmarkName(old.getBookmarkName()); + } + Conversation conversation = find(bookmark); + if (conversation != null) { + conversation.setBookmark(bookmark); + } else if (bookmark.autojoin() && bookmark.getJid() != null && autojoin) { + conversation = findOrCreateConversation( + account, bookmark.getJid(), true); + conversation.setBookmark(bookmark); + joinMuc(conversation); + } } } } + account.setBookmarks(new ArrayList<>(bookmarks.values())); + } else { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not fetch bookmarks"); } - account.setBookmarks(bookmarks); } }; sendIqPacket(account, iqPacket, callback); } public void pushBookmarks(Account account) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid()+": pushing bookmarks"); IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET); Element query = iqPacket.query("jabber:iq:private"); Element storage = query.addChild("storage", "storage:bookmarks"); @@ -855,7 +1058,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa mPhoneContactMergerThread = new Thread(new Runnable() { @Override public void run() { - Logging.d(Config.LOGTAG,"start merging phone contacts with roster"); + Logging.d(Config.LOGTAG, "start merging phone contacts with roster"); for (Account account : accounts) { List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts(); for (Bundle phoneContact : phoneContacts) { @@ -871,8 +1074,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } final Contact contact = account.getRoster().getContact(jid); String systemAccount = phoneContact.getInt("phoneid") - + "#" - + phoneContact.getString("lookup"); + + "#" + + phoneContact.getString("lookup"); contact.setSystemAccount(systemAccount); if (contact.setPhotoUri(phoneContact.getString("photouri"))) { AvatarService.getInstance().clear(contact); @@ -880,7 +1083,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa contact.setSystemName(phoneContact.getString("displayname")); withSystemAccounts.remove(contact); } - for(Contact contact : withSystemAccounts) { + for (Contact contact : withSystemAccounts) { contact.setSystemAccount(null); contact.setSystemName(null); if (contact.setPhotoUri(null)) { @@ -906,23 +1109,29 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa Account account = accountLookupTable.get(conversation.getAccountUuid()); conversation.setAccount(account); } - Runnable runnable =new Runnable() { + Runnable runnable = new Runnable() { @Override public void run() { - Logging.d(Config.LOGTAG,"restoring roster"); - for(Account account : accounts) { + Logging.d(Config.LOGTAG, "restoring roster"); + for (Account account : accounts) { databaseBackend.readRoster(account.getRoster()); + account.initAccountServices(XmppConnectionService.this); //roster needs to be loaded at this stage } ImageUtil.evictBitmapCache(); Looper.prepare(); - PhoneHelper.loadPhoneContacts(getApplicationContext(), - new CopyOnWriteArrayList<Bundle>(), - XmppConnectionService.this); - Logging.d(Config.LOGTAG,"restoring messages"); + loadPhoneContacts(); + Logging.d(Config.LOGTAG, "restoring messages"); for (Conversation conversation : conversations) { conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); checkDeletedFiles(conversation); + conversation.findUnreadMessages(new Conversation.OnMessageFound() { + @Override + public void onMessageFound(Message message) { + mNotificationService.pushFromBacklog(message); + } + }); } + mNotificationService.finishBacklog(false); mRestoredFromDatabase = true; Logging.d(Config.LOGTAG,"restored all messages"); updateConversationUi(); @@ -932,6 +1141,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void loadPhoneContacts() { + PhoneHelper.loadPhoneContacts(getApplicationContext(), + new CopyOnWriteArrayList<Bundle>(), + XmppConnectionService.this); + } + public List<Conversation> getConversations() { return this.conversations; } @@ -943,6 +1158,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void onMessageFound(Message message) { if (!FileBackend.isFileAvailable(message)) { message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + final int s = message.getStatus(); + if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { + markMessage(message, Message.STATUS_SEND_FAILED); + } } } }); @@ -954,7 +1173,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (message != null) { if (!FileBackend.isFileAvailable(message)) { message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - updateConversationUi(); + final int s = message.getStatus(); + if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) { + markMessage(message, Message.STATUS_SEND_FAILED); + } else { + updateConversationUi(); + } } return; } @@ -994,12 +1218,15 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void loadMoreMessages(final Conversation conversation, final long timestamp, final OnMoreMessagesLoaded callback) { - Logging.d(Config.LOGTAG, "load more messages for " + conversation.getName() + " prior to " + MessageGenerator.getTimestamp(timestamp)); - if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation,callback)) { + if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation, callback)) { Logging.d("mam", "Query in progress"); return; + } else if (timestamp == 0) { + Logging.d("mam", "Query stopped due to timestamp"); + return; } //TODO Create a separate class for this runnable to store if messages are getting loaded or not. Not really a good idea to do this in the callback. + Logging.d(Config.LOGTAG, "load more messages for " + conversation.getName() + " prior to " + MessageGenerator.getTimestamp(timestamp)); Runnable runnable = new Runnable() { @Override public void run() { @@ -1007,30 +1234,32 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (null != callback) { callback.setLoadingInProgress(); // Tell the callback that the loading is in progress } - final Account account = conversation.getAccount(); - List<Message> messages = databaseBackend.getMessages(conversation, 50, timestamp); + final Account account = conversation.getAccount(); + List<Message> messages = databaseBackend.getMessages(conversation, 50, timestamp); Logging.d("mam", "runnable load more messages"); - if (messages.size() > 0) { + if (messages.size() > 0) { Logging.d("mam", "At least one message"); - conversation.addAll(0, messages); - checkDeletedFiles(conversation); - callback.onMoreMessagesLoaded(messages.size(), conversation); - } else if (conversation.hasMessagesLeftOnServer() - && account.isOnlineAndConnected() - && account.getXmppConnection().getFeatures().mam()) { + conversation.addAll(0, messages); + checkDeletedFiles(conversation); + callback.onMoreMessagesLoaded(messages.size(), conversation); + } else if (conversation.hasMessagesLeftOnServer() + && account.isOnlineAndConnected()) { Logging.d("mam", "mam activate, account online and connected and messages left on server"); - MessageArchiveService.Query query = getMessageArchiveService().query(conversation, 0, timestamp - 1); - if (query != null) { - query.setCallback(callback); - } - callback.informUser(R.string.fetching_history_from_server); - } else { + if ((conversation.getMode() == Conversation.MODE_SINGLE && account.getXmppConnection().getFeatures().mam()) + || (conversation.getMode() == Conversation.MODE_MULTI && conversation.getMucOptions().mamSupport())) { + MessageArchiveService.Query query = getMessageArchiveService().query(conversation, 0, timestamp - 1); + if (query != null) { + query.setCallback(callback); + } + callback.informUser(R.string.fetching_history_from_server); + } + } else { Logging.d("mam", ((!conversation.hasMessagesLeftOnServer()) ? "no" : "") + " more messages left on server, mam " + ((account.getXmppConnection().getFeatures().mam()) ? "" : "not") + " activated, account is " + ((account.isOnlineAndConnected()) ? "" : "not") + " online or connected)"); callback.onMoreMessagesLoaded(0, conversation); callback.informUser(R.string.no_more_history_on_server); } + } } - } }; ConversationsPlusApplication.executeDatabaseOperation(runnable); } @@ -1128,7 +1357,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getAccount().getStatus() == Account.State.ONLINE) { Bookmark bookmark = conversation.getBookmark(); - if (bookmark != null && bookmark.autojoin()) { + if (bookmark != null && bookmark.autojoin() && respectAutojoin()) { bookmark.setAutojoin(false); pushBookmarks(bookmark.getAccount()); } @@ -1136,6 +1365,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa leaveMuc(conversation); } else { conversation.endOtrIfNeeded(); + if (conversation.getContact().getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { + Log.d(Config.LOGTAG, "Canceling presence request from " + conversation.getJid().toString()); + sendPresencePacket( + conversation.getAccount(), + mPresenceGenerator.stopPresenceUpdatesTo(conversation.getContact()) + ); + } } this.databaseBackend.updateConversation(conversation); this.conversations.remove(conversation); @@ -1151,10 +1387,68 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa updateAccountUi(); } + public void createAccountFromKey(final String alias, final OnAccountCreated callback) { + new Thread(new Runnable() { + @Override + public void run() { + try { + X509Certificate[] chain = KeyChain.getCertificateChain(XmppConnectionService.this, alias); + Pair<Jid, String> info = CryptoHelper.extractJidAndName(chain[0]); + if (findAccountByJid(info.first) == null) { + Account account = new Account(info.first, ""); + account.setPrivateKeyAlias(alias); + account.setOption(Account.OPTION_DISABLED, true); + account.setDisplayName(info.second); + createAccount(account); + callback.onAccountCreated(account); + if (Config.X509_VERIFICATION) { + try { + getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); + } catch (CertificateException e) { + callback.informUser(R.string.certificate_chain_is_not_trusted); + } + } + } else { + callback.informUser(R.string.account_already_exists); + } + } catch (Exception e) { + e.printStackTrace(); + callback.informUser(R.string.unable_to_parse_certificate); + } + } + }).start(); + + } + + public void updateKeyInAccount(final Account account, final String alias) { + Log.d(Config.LOGTAG, "update key in account " + alias); + try { + X509Certificate[] chain = KeyChain.getCertificateChain(XmppConnectionService.this, alias); + Pair<Jid, String> info = CryptoHelper.extractJidAndName(chain[0]); + if (account.getJid().toBareJid().equals(info.first)) { + account.setPrivateKeyAlias(alias); + account.setDisplayName(info.second); + databaseBackend.updateAccount(account); + if (Config.X509_VERIFICATION) { + try { + getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); + } catch (CertificateException e) { + showErrorToastInUi(R.string.certificate_chain_is_not_trusted); + } + account.getAxolotlService().regenerateKeys(true); + } + } else { + showErrorToastInUi(R.string.jid_does_not_match_certificate); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + public void updateAccount(final Account account) { this.statusListener.onStatusChanged(account); databaseBackend.updateAccount(account); - reconnectAccount(account, false); + reconnectAccountInBackground(account); updateAccountUi(); getNotificationService().updateErrorNotification(); } @@ -1190,7 +1484,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (account.getXmppConnection() != null) { this.disconnect(account, true); } - databaseBackend.deleteAccount(account); + Runnable runnable = new Runnable() { + @Override + public void run() { + databaseBackend.deleteAccount(account); + } + }; + ConversationsPlusApplication.executeDatabaseOperation(runnable); this.accounts.remove(account); updateAccountUi(); getNotificationService().updateErrorNotification(); @@ -1275,6 +1575,31 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void setOnCaptchaRequestedListener(OnCaptchaRequested listener) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnCaptchaRequested = listener; + if (this.captchaRequestedListenerCount < 2) { + this.captchaRequestedListenerCount++; + } + } + } + + public void removeOnCaptchaRequestedListener() { + synchronized (this) { + this.captchaRequestedListenerCount--; + if (this.captchaRequestedListenerCount <= 0) { + this.mOnCaptchaRequested = null; + this.captchaRequestedListenerCount = 0; + if (checkListeners()) { + switchToBackground(); + } + } + } + } + public void setOnRosterUpdateListener(final OnRosterUpdate listener) { synchronized (this) { if (checkListeners()) { @@ -1325,6 +1650,31 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void setOnKeyStatusUpdatedListener(final OnKeyStatusUpdated listener) { + synchronized (this) { + if (checkListeners()) { + switchToForeground(); + } + this.mOnKeyStatusUpdated = listener; + if (this.keyStatusUpdatedListenerCount < 2) { + this.keyStatusUpdatedListenerCount++; + } + } + } + + public void removeOnNewKeysAvailableListener() { + synchronized (this) { + this.keyStatusUpdatedListenerCount--; + if (this.keyStatusUpdatedListenerCount <= 0) { + this.keyStatusUpdatedListenerCount = 0; + this.mOnKeyStatusUpdated = null; + if (checkListeners()) { + switchToBackground(); + } + } + } + } + public void setOnMucRosterUpdateListener(OnMucRosterUpdate listener) { synchronized (this) { if (checkListeners()) { @@ -1354,11 +1704,16 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return (this.mOnAccountUpdate == null && this.mOnConversationUpdate == null && this.mOnRosterUpdate == null + && this.mOnCaptchaRequested == null && this.mOnUpdateBlocklist == null - && this.mOnShowErrorToast == null); + && this.mOnShowErrorToast == null + && this.mOnKeyStatusUpdated == null); } private void switchToForeground() { + for (Conversation conversation : getConversations()) { + conversation.setIncomingChatState(ChatState.ACTIVE); + } for (Account account : getAccounts()) { if (account.getStatus() == Account.State.ONLINE) { XmppConnection connection = account.getXmppConnection(); @@ -1374,14 +1729,17 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa for (Account account : getAccounts()) { if (account.getStatus() == Account.State.ONLINE) { XmppConnection connection = account.getXmppConnection(); - if (connection != null && connection.getFeatures().csi()) { - connection.sendInactive(); + if (connection != null) { + if (connection.getFeatures().csi()) { + connection.sendInactive(); + } + if (Config.CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND && mPushManagementService.available(account)) { + connection.waitForPush(); + cancelWakeUpCall(account.getUuid().hashCode()); + } } } } - for(Conversation conversation : getConversations()) { - conversation.setIncomingChatState(ChatState.ACTIVE); - } this.mNotificationService.setIsInForeground(false); Logging.d(Config.LOGTAG, "app switched into background"); } @@ -1389,47 +1747,80 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private void connectMultiModeConversations(Account account) { List<Conversation> conversations = getConversations(); for (Conversation conversation : conversations) { - if ((conversation.getMode() == Conversation.MODE_MULTI) - && (conversation.getAccount() == account)) { - conversation.resetMucOptions(); + if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getAccount() == account) { joinMuc(conversation); } } } public void joinMuc(Conversation conversation) { + joinMuc(conversation, null); + } + + private void joinMuc(Conversation conversation, final OnConferenceJoined onConferenceJoined) { Account account = conversation.getAccount(); account.pendingConferenceJoins.remove(conversation); account.pendingConferenceLeaves.remove(conversation); if (account.getStatus() == Account.State.ONLINE) { - final String nick = conversation.getMucOptions().getProposedNick(); - final Jid joinJid = conversation.getMucOptions().createJoinJid(nick); - if (joinJid == null) { - return; //safety net - } - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString()); - PresencePacket packet = new PresencePacket(); - packet.setFrom(conversation.getAccount().getJid()); - packet.setTo(joinJid); - Element x = packet.addChild("x", "http://jabber.org/protocol/muc"); - if (conversation.getMucOptions().getPassword() != null) { - x.addChild("password").setContent(conversation.getMucOptions().getPassword()); - } - x.addChild("history").setAttribute("since", PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted())); - String sig = account.getPgpSignature(); - if (sig != null) { - packet.addChild("status").setContent("online"); - packet.addChild("x", "jabber:x:signed").setContent(sig); - } - sendPresencePacket(account, packet); - fetchConferenceConfiguration(conversation); - if (!joinJid.equals(conversation.getJid())) { - conversation.setContactJid(joinJid); - databaseBackend.updateConversation(conversation); - } + conversation.resetMucOptions(); conversation.setHasMessagesLeftOnServer(false); + fetchConferenceConfiguration(conversation, new OnConferenceConfigurationFetched() { + + private void join(Conversation conversation) { + Account account = conversation.getAccount(); + final String nick = conversation.getMucOptions().getProposedNick(); + final Jid joinJid = conversation.getMucOptions().createJoinJid(nick); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString()); + PresencePacket packet = new PresencePacket(); + packet.setFrom(conversation.getAccount().getJid()); + packet.setTo(joinJid); + Element x = packet.addChild("x", "http://jabber.org/protocol/muc"); + if (conversation.getMucOptions().getPassword() != null) { + x.addChild("password").setContent(conversation.getMucOptions().getPassword()); + } + + if (conversation.getMucOptions().mamSupport()) { + // Use MAM instead of the limited muc history to get history + x.addChild("history").setAttribute("maxchars", "0"); + } else { + // Fallback to muc history + x.addChild("history").setAttribute("since", PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted())); + } + String sig = account.getPgpSignature(); + if (sig != null) { + packet.addChild("x", "jabber:x:signed").setContent(sig); + } + sendPresencePacket(account, packet); + if (onConferenceJoined != null) { + onConferenceJoined.onConferenceJoined(conversation); + } + if (!joinJid.equals(conversation.getJid())) { + conversation.setContactJid(joinJid); + databaseBackend.updateConversation(conversation); + } + if (conversation.getMucOptions().mamSupport()) { + getMessageArchiveService().catchupMUC(conversation); + } + sendUnsentMessages(conversation); + } + + @Override + public void onConferenceConfigurationFetched(Conversation conversation) { + join(conversation); + } + + @Override + public void onFetchFailed(final Conversation conversation, Element error) { + join(conversation); + fetchConferenceConfiguration(conversation); + } + }); + updateConversationUi(); } else { account.pendingConferenceJoins.add(conversation); + conversation.resetMucOptions(); + conversation.setHasMessagesLeftOnServer(false); + updateConversationUi(); } } @@ -1437,7 +1828,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (conversation.getMode() == Conversation.MODE_MULTI) { conversation.getMucOptions().setPassword(password); if (conversation.getBookmark() != null) { - conversation.getBookmark().setAutojoin(true); + if (respectAutojoin()) { + conversation.getBookmark().setAutojoin(true); + } pushBookmarks(conversation.getAccount()); } databaseBackend.updateConversation(conversation); @@ -1495,10 +1888,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void leaveMuc(Conversation conversation) { + leaveMuc(conversation, false); + } + + private void leaveMuc(Conversation conversation, boolean now) { Account account = conversation.getAccount(); account.pendingConferenceJoins.remove(conversation); account.pendingConferenceLeaves.remove(conversation); - if (account.getStatus() == Account.State.ONLINE) { + if (account.getStatus() == Account.State.ONLINE || now) { PresencePacket packet = new PresencePacket(); packet.setTo(conversation.getJid()); packet.setFrom(conversation.getAccount().getJid()); @@ -1546,34 +1943,37 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa String name = new BigInteger(75, getRNG()).toString(32); Jid jid = Jid.fromParts(name, server, null); final Conversation conversation = findOrCreateConversation(account, jid, true); - joinMuc(conversation); - Bundle options = new Bundle(); - options.putString("muc#roomconfig_persistentroom", "1"); - options.putString("muc#roomconfig_membersonly", "1"); - options.putString("muc#roomconfig_publicroom", "0"); - options.putString("muc#roomconfig_whois", "anyone"); - pushConferenceConfiguration(conversation, options, new OnConferenceOptionsPushed() { + joinMuc(conversation, new OnConferenceJoined() { @Override - public void onPushSucceeded() { - for (Jid invite : jids) { - invite(conversation, invite); - } - if (account.countPresences() > 1) { - directInvite(conversation, account.getJid().toBareJid()); - } - if (callback != null) { - callback.success(conversation); - } - } + public void onConferenceJoined(final Conversation conversation) { + Bundle options = new Bundle(); + options.putString("muc#roomconfig_persistentroom", "1"); + options.putString("muc#roomconfig_membersonly", "1"); + options.putString("muc#roomconfig_publicroom", "0"); + options.putString("muc#roomconfig_whois", "anyone"); + pushConferenceConfiguration(conversation, options, new OnConferenceOptionsPushed() { + @Override + public void onPushSucceeded() { + for (Jid invite : jids) { + invite(conversation, invite); + } + if (account.countPresences() > 1) { + directInvite(conversation, account.getJid().toBareJid()); + } + if (callback != null) { + callback.success(conversation); + } + } - @Override - public void onPushFailed() { - if (callback != null) { - callback.error(R.string.conference_creation_failed, conversation); - } + @Override + public void onPushFailed() { + if (callback != null) { + callback.error(R.string.conference_creation_failed, conversation); + } + } + }); } }); - } catch (InvalidJidException e) { if (callback != null) { callback.error(R.string.conference_creation_failed, null); @@ -1587,15 +1987,20 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void fetchConferenceConfiguration(final Conversation conversation) { + fetchConferenceConfiguration(conversation, null); + } + + public void fetchConferenceConfiguration(final Conversation conversation, final OnConferenceConfigurationFetched callback) { IqPacket request = new IqPacket(IqPacket.TYPE.GET); request.setTo(conversation.getJid().toBareJid()); request.query("http://jabber.org/protocol/disco#info"); sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() != IqPacket.TYPE.ERROR) { + if (packet.getType() == IqPacket.TYPE.RESULT) { ArrayList<String> features = new ArrayList<>(); - for (Element child : packet.query().getChildren()) { + Element query = packet.query(); + for (Element child : query.getChildren()) { if (child != null && child.getName().equals("feature")) { String var = child.getAttribute("var"); if (var != null) { @@ -1603,8 +2008,19 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } } + Element form = query.findChild("x", "jabber:x:data"); + if (form != null) { + conversation.getMucOptions().updateFormData(Data.parse(form)); + } conversation.getMucOptions().updateFeatures(features); + if (callback != null) { + callback.onConferenceConfigurationFetched(conversation); + } updateConversationUi(); + } else if (packet.getType() == IqPacket.TYPE.ERROR) { + if (callback != null) { + callback.onFetchFailed(conversation, packet.getError()); + } } } }); @@ -1617,11 +2033,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() != IqPacket.TYPE.ERROR) { + if (packet.getType() == IqPacket.TYPE.RESULT) { Data data = Data.parse(packet.query().findChild("x", "jabber:x:data")); for (Field field : data.getFields()) { - if (options.containsKey(field.getName())) { - field.setValue(options.getString(field.getName())); + if (options.containsKey(field.getFieldName())) { + field.setValue(options.getString(field.getFieldName())); } } data.submit(); @@ -1631,12 +2047,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa sendIqPacket(account, set, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - if (callback != null) { + if (callback != null) { + if (packet.getType() == IqPacket.TYPE.RESULT) { callback.onPushSucceeded(); - } - } else { - if (callback != null) { + } else { callback.onPushFailed(); } } @@ -1705,7 +2119,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa }); } - public void disconnect(Account account, boolean force) { + private void disconnect(Account account, boolean force) { if ((account.getStatus() == Account.State.ONLINE) || (account.getStatus() == Account.State.DISABLED)) { if (!force) { @@ -1713,7 +2127,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa for (Conversation conversation : conversations) { if (conversation.getAccount() == account) { if (conversation.getMode() == Conversation.MODE_MULTI) { - leaveMuc(conversation); + leaveMuc(conversation, true); } else { if (conversation.endOtrIfNeeded()) { Logging.d(Config.LOGTAG, account.getJid().toBareJid() @@ -1739,6 +2153,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa updateConversationUi(); } + public void updateMessage(Message message, String uuid) { + databaseBackend.updateMessage(message, uuid); + updateConversationUi(); + } + protected void syncDirtyContacts(Account account) { for (Contact contact : account.getRoster().getContacts()) { if (contact.getOption(Contact.Options.DIRTY_PUSH)) { @@ -1765,7 +2184,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa account.getJid().toBareJid() + " otr session established with " + conversation.getJid() + "/" + otrSession.getSessionID().getUserID()); - conversation.findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() { + conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() { @Override public void onMessageFound(Message message) { @@ -1778,8 +2197,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (message.needsUploading()) { mJingleConnectionManager.createNewConnection(message); } else { - MessagePacket outPacket = mMessageGenerator.generateOtrChat(message, true); + MessagePacket outPacket = mMessageGenerator.generateOtrChat(message); if (outPacket != null) { + mMessageGenerator.addDelay(outPacket, message.getTimeSent()); message.setStatus(Message.STATUS_SEND); databaseBackend.updateMessage(message); sendMessagePacket(account, outPacket); @@ -1799,8 +2219,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa MessagePacket packet = new MessagePacket(); packet.setType(MessagePacket.TYPE_CHAT); packet.setFrom(account.getJid()); - packet.addChild("private", "urn:xmpp:carbons:2"); - packet.addChild("no-copy", "urn:xmpp:hints"); + MessageGenerator.addMessageHints(packet); packet.setAttribute("to", otrSession.getSessionID().getAccountID() + "/" + otrSession.getSessionID().getUserID()); try { @@ -1858,24 +2277,39 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa this.databaseBackend.updateConversation(conversation); } - public void reconnectAccount(final Account account, final boolean force) { + private void reconnectAccount(final Account account, final boolean force, final boolean interactive) { synchronized (account) { - if (account.getXmppConnection() != null) { + XmppConnection connection = account.getXmppConnection(); + if (connection != null) { disconnect(account, force); + } else { + connection = createConnection(account); + account.setXmppConnection(connection); } if (!account.isOptionSet(Account.OPTION_DISABLED)) { - - AvatarService.getInstance().clearFetchInProgress(account); - - if (account.getXmppConnection() == null) { - account.setXmppConnection(createConnection(account)); + synchronized (this.mInProgressAvatarFetches) { + for (Iterator<String> iterator = this.mInProgressAvatarFetches.iterator(); iterator.hasNext(); ) { + final String KEY = iterator.next(); + if (KEY.startsWith(account.getJid().toBareJid() + "_")) { + iterator.remove(); + } + } + } + if (!force) { + try { + Logging.d(Config.LOGTAG, "wait for disconnect"); + Thread.sleep(500); //sleep wait for disconnect + } catch (InterruptedException e) { + //ignored + } } - Thread thread = new Thread(account.getXmppConnection()); + Thread thread = new Thread(connection); + connection.setInteractive(interactive); thread.start(); - scheduleWakeUpCall(Config.CONNECT_TIMEOUT, account.getUuid().hashCode()); + scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode()); } else { account.getRoster().clearPresences(); - account.setXmppConnection(null); + connection.resetEverything(); } } } @@ -1884,7 +2318,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa new Thread(new Runnable() { @Override public void run() { - reconnectAccount(account,false); + reconnectAccount(account, false, true); } }).start(); } @@ -1920,7 +2354,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } for (Conversation conversation : getConversations()) { if (conversation.getJid().toBareJid().equals(recipient) && conversation.getAccount() == account) { - final Message message = conversation.findSentMessageWithUuid(uuid); + final Message message = conversation.findSentMessageWithUuidOrRemoteId(uuid); if (message != null) { markMessage(message, status); } @@ -1930,8 +2364,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return null; } - public boolean markMessage(Conversation conversation, String uuid, - int status) { + public boolean markMessage(Conversation conversation, String uuid, int status) { if (uuid == null) { return false; } else { @@ -1956,9 +2389,26 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa updateConversationUi(); } + public SharedPreferences getPreferences() { + return PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + } + + public boolean saveEncryptedMessages() { + return !getPreferences().getBoolean("dont_save_encrypted", false); + } + + private boolean respectAutojoin() { + return getPreferences().getBoolean("autojoin", true); + } + + public boolean showExtendedConnectionOptions() { + return getPreferences().getBoolean("show_connection_options", false); + } + public int unreadCount() { int count = 0; - for(Conversation conversation : getConversations()) { + for (Conversation conversation : getConversations()) { count += conversation.unreadCount(); } return count; @@ -1989,6 +2439,20 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public boolean displayCaptchaRequest(Account account, String id, Data data, Bitmap captcha) { + boolean rc = false; + if (mOnCaptchaRequested != null) { + DisplayMetrics metrics = getApplicationContext().getResources().getDisplayMetrics(); + Bitmap scaled = Bitmap.createScaledBitmap(captcha, (int) (captcha.getWidth() * metrics.scaledDensity), + (int) (captcha.getHeight() * metrics.scaledDensity), false); + + mOnCaptchaRequested.onCaptchaRequested(account, id, data, scaled); + rc = true; + } + + return rc; + } + public void updateBlocklistUi(final OnUpdateBlocklist.Status status) { if (mOnUpdateBlocklist != null) { mOnUpdateBlocklist.OnUpdateBlocklist(status); @@ -2001,6 +2465,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void keyStatusUpdated(AxolotlService.FetchStatus report) { + if (mOnKeyStatusUpdated != null) { + mOnKeyStatusUpdated.onKeyStatusUpdated(report); + } + } + public Account findAccountByJid(final Jid accountJid) { for (Account account : this.accounts) { if (account.getJid().toBareJid().equals(accountJid.toBareJid())) { @@ -2019,10 +2489,24 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return null; } - public void markRead(final Conversation conversation) { + public boolean markRead(final Conversation conversation) { mNotificationService.clear(conversation); - conversation.markRead(); - updateUnreadCountBadge(); + final List<Message> readMessages = conversation.markRead(); + if (readMessages.size() > 0) { + Runnable runnable = new Runnable() { + @Override + public void run() { + for (Message message : readMessages) { + databaseBackend.updateMessage(message); + } + } + }; + ConversationsPlusApplication.executeDatabaseOperation(runnable); + updateUnreadCountBadge(); + return true; + } else { + return false; + } } public synchronized void updateUnreadCountBadge() { @@ -2040,7 +2524,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void sendReadMarker(final Conversation conversation) { final Message markable = conversation.getLatestMarkableMessage(); - this.markRead(conversation); + if (this.markRead(conversation)) { + updateConversationUi(); + } if (Settings.CONFIRM_MESSAGE_READ && markable != null && markable.getRemoteMsgId() != null) { Logging.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending read marker to " + markable.getCounterpart().toString()); Account account = conversation.getAccount(); @@ -2048,7 +2534,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa MessagePacket packet = mMessageGenerator.confirm(account, to, markable.getRemoteMsgId()); this.sendMessagePacket(conversation.getAccount(), packet); } - updateConversationUi(); } public SecureRandom getRNG() { @@ -2077,6 +2562,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return this.pm; } + public LruCache<String, Bitmap> getBitmapCache() { + return this.mBitmapCache; + } + public void syncRosterToDisk(final Account account) { Runnable runnable = new Runnable() { @@ -2119,27 +2608,50 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return mucServers; } + @Deprecated public void sendMessagePacket(Account account, MessagePacket packet) { - XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - connection.sendMessagePacket(packet); - } + XmppSendUtil.sendMessagePacket(account, packet); } + @Deprecated public void sendPresencePacket(Account account, PresencePacket packet) { XmppSendUtil.sendPresencePacket(account, packet); } + public void sendCreateAccountWithCaptchaPacket(Account account, String id, Data data) { + XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + connection.sendCaptchaRegistryRequest(id, data); + } + } + + @Deprecated public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) { XmppSendUtil.sendIqPacket(account, packet, callback); } public void sendPresence(final Account account) { - sendPresencePacket(account, mPresenceGenerator.sendPresence(account)); + XmppSendUtil.sendPresencePacket(account, mPresenceGenerator.selfPresence(account, getTargetPresence())); + } + + public void refreshAllPresences() { + for (Account account : getAccounts()) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + sendPresence(account); + } + } + } + + private void refreshAllGcmTokens() { + for(Account account : getAccounts()) { + if (account.isOnlineAndConnected() && mPushManagementService.available(account)) { + mPushManagementService.registerPushTokenOnServer(account); + } + } } public void sendOfflinePresence(final Account account) { - sendPresencePacket(account, mPresenceGenerator.sendOfflinePresence(account)); + XmppSendUtil.sendPresencePacket(account, mPresenceGenerator.sendOfflinePresence(account)); } public MessageGenerator getMessageGenerator() { @@ -2199,8 +2711,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } for (final Message msg : messages) { + msg.setTime(System.currentTimeMillis()); markMessage(msg, Message.STATUS_WAITING); - this.resendMessage(msg); + this.resendMessage(msg, false); } } @@ -2212,12 +2725,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa * Therefore set this flag to true and try to get messages from server */ conversation.setHasMessagesLeftOnServer(true); - new Thread(new Runnable() { + Runnable runnable = new Runnable() { @Override public void run() { databaseBackend.deleteMessagesInConversation(conversation); } - }).start(); + }; + ConversationsPlusApplication.executeDatabaseOperation(runnable); } public void sendBlockRequest(final Blockable blockable) { @@ -2251,10 +2765,116 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void publishDisplayName(Account account) { + String displayName = account.getDisplayName(); + if (displayName != null && !displayName.isEmpty()) { + IqPacket publish = mIqGenerator.publishNick(displayName); + sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.ERROR) { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not publish nick"); + } + } + }); + } + } + + private ServiceDiscoveryResult getCachedServiceDiscoveryResult(Pair<String,String> key) { + ServiceDiscoveryResult result = discoCache.get(key); + if (result != null) { + return result; + } else { + result = databaseBackend.findDiscoveryResult(key.first, key.second); + if (result != null) { + discoCache.put(key, result); + } + return result; + } + } + + public void fetchCaps(Account account, final Jid jid, final Presence presence) { + final Pair<String,String> key = new Pair<>(presence.getHash(), presence.getVer()); + ServiceDiscoveryResult disco = getCachedServiceDiscoveryResult(key); + if (disco != null) { + presence.setServiceDiscoveryResult(disco); + } else { + if (!account.inProgressDiscoFetches.contains(key)) { + account.inProgressDiscoFetches.add(key); + IqPacket request = new IqPacket(IqPacket.TYPE.GET); + request.setTo(jid); + request.query("http://jabber.org/protocol/disco#info"); + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": making disco request for "+key.second+" to "+jid); + sendIqPacket(account, request, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket discoPacket) { + if (discoPacket.getType() == IqPacket.TYPE.RESULT) { + ServiceDiscoveryResult disco = new ServiceDiscoveryResult(discoPacket); + if (presence.getVer().equals(disco.getVer())) { + databaseBackend.insertDiscoveryResult(disco); + injectServiceDiscorveryResult(account.getRoster(), presence.getHash(), presence.getVer(), disco); + } else { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + disco.getVer()); + } + } + account.inProgressDiscoFetches.remove(key); + } + }); + } + } + } + + private void injectServiceDiscorveryResult(Roster roster, String hash, String ver, ServiceDiscoveryResult disco) { + for(Contact contact : roster.getContacts()) { + for(Presence presence : contact.getPresences().getPresences().values()) { + if (hash.equals(presence.getHash()) && ver.equals(presence.getVer())) { + presence.setServiceDiscoveryResult(disco); + } + } + } + } + + public void fetchMamPreferences(Account account, final OnMamPreferencesFetched callback) { + IqPacket request = new IqPacket(IqPacket.TYPE.GET); + request.addChild("prefs","urn:xmpp:mam:0"); + sendIqPacket(account, request, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Element prefs = packet.findChild("prefs","urn:xmpp:mam:0"); + if (packet.getType() == IqPacket.TYPE.RESULT && prefs != null) { + callback.onPreferencesFetched(prefs); + } else { + callback.onPreferencesFetchFailed(); + } + } + }); + } + + public PushManagementService getPushManagementService() { + return mPushManagementService; + } + + public interface OnMamPreferencesFetched { + void onPreferencesFetched(Element prefs); + void onPreferencesFetchFailed(); + } + + public void pushMamPreferences(Account account, Element prefs) { + IqPacket set = new IqPacket(IqPacket.TYPE.SET); + set.addChild(prefs); + sendIqPacket(account, set, null); + } + + public interface OnAccountCreated { + void onAccountCreated(Account account); + + void informUser(int r); + } + public interface OnMoreMessagesLoaded { - public void onMoreMessagesLoaded(int count, Conversation conversation); + void onMoreMessagesLoaded(int count, Conversation conversation); - public void informUser(int r); + void informUser(int r); void setLoadingInProgress(); @@ -2262,43 +2882,60 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public interface OnAccountPasswordChanged { - public void onPasswordChangeSucceeded(); + void onPasswordChangeSucceeded(); - public void onPasswordChangeFailed(); + void onPasswordChangeFailed(); } public interface OnAffiliationChanged { - public void onAffiliationChangedSuccessful(Jid jid); + void onAffiliationChangedSuccessful(Jid jid); - public void onAffiliationChangeFailed(Jid jid, int resId); + void onAffiliationChangeFailed(Jid jid, int resId); } public interface OnRoleChanged { - public void onRoleChangedSuccessful(String nick); + void onRoleChangedSuccessful(String nick); - public void onRoleChangeFailed(String nick, int resid); + void onRoleChangeFailed(String nick, int resid); } public interface OnConversationUpdate { - public void onConversationUpdate(); + void onConversationUpdate(); } public interface OnAccountUpdate { - public void onAccountUpdate(); + void onAccountUpdate(); + } + + public interface OnCaptchaRequested { + void onCaptchaRequested(Account account, + String id, + Data data, + Bitmap captcha); } public interface OnRosterUpdate { - public void onRosterUpdate(); + void onRosterUpdate(); } public interface OnMucRosterUpdate { - public void onMucRosterUpdate(); + void onMucRosterUpdate(); + } + + public interface OnConferenceConfigurationFetched { + void onConferenceConfigurationFetched(Conversation conversation); + + void onFetchFailed(Conversation conversation, Element error); + } + + public interface OnConferenceJoined { + void onConferenceJoined(Conversation conversation); } public interface OnConferenceOptionsPushed { - public void onPushSucceeded(); + void onPushSucceeded(); - public void onPushFailed(); + void onPushFailed(); } public interface OnShowErrorToast { diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/BlocklistActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/BlocklistActivity.java index 4e6d7701..377d0e02 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/BlocklistActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/BlocklistActivity.java @@ -35,7 +35,7 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem @Override public void onBackendConnected() { for (final Account account : xmppConnectionService.getAccounts()) { - if (account.getJid().toString().equals(getIntent().getStringExtra("account"))) { + if (account.getJid().toString().equals(getIntent().getStringExtra(EXTRA_ACCOUNT))) { this.account = account; break; } @@ -55,16 +55,10 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem } Collections.sort(getListItems()); } - runOnUiThread(new Runnable() { - @Override - public void run() { - getListItemAdapter().notifyDataSetChanged(); - } - }); + getListItemAdapter().notifyDataSetChanged(); } - @Override - public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { + protected void refreshUiReal() { final Editable editable = getSearchEditText().getText(); if (editable != null) { filterContacts(editable.toString()); @@ -72,4 +66,9 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem filterContacts(); } } + + @Override + public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) { + refreshUi(); + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ChangePasswordActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ChangePasswordActivity.java index 9d6b50fc..1eb9fa05 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ChangePasswordActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ChangePasswordActivity.java @@ -9,8 +9,6 @@ import android.widget.Toast; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.services.XmppConnectionService; -import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; -import de.thedevstack.conversationsplus.xmpp.jid.Jid; public class ChangePasswordActivity extends XmppActivity implements XmppConnectionService.OnAccountPasswordChanged { @@ -53,14 +51,7 @@ public class ChangePasswordActivity extends XmppActivity implements XmppConnecti @Override void onBackendConnected() { - try { - final String jid = getIntent() == null ? null : getIntent().getStringExtra("account"); - if (jid != null) { - this.mAccount = xmppConnectionService.findAccountByJid(Jid.fromString(jid)); - } - } catch (final InvalidJidException ignored) { - - } + this.mAccount = extractAccount(getIntent()); } @@ -106,4 +97,8 @@ public class ChangePasswordActivity extends XmppActivity implements XmppConnecti }); } + + public void refreshUiReal() { + + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ChooseContactActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ChooseContactActivity.java index d51d23e1..29860434 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ChooseContactActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ChooseContactActivity.java @@ -13,18 +13,22 @@ import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.ListView; -import java.util.Set; -import java.util.HashSet; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; -import java.util.ArrayList; +import java.util.Set; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.ListItem; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; public class ChooseContactActivity extends AbstractSearchableListItemActivity { + private List<String> mActivatedAccounts = new ArrayList<String>(); + private List<String> mKnownHosts; private Set<Contact> selected; private Set<String> filterContacts; @@ -109,11 +113,11 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { final Intent data = new Intent(); final ListItem mListItem = getListItems().get(position); data.putExtra("contact", mListItem.getJid().toString()); - String account = request.getStringExtra("account"); + String account = request.getStringExtra(EXTRA_ACCOUNT); if (account == null && mListItem instanceof Contact) { account = ((Contact) mListItem).getAccount().getJid().toBareJid().toString(); } - data.putExtra("account", account); + data.putExtra(EXTRA_ACCOUNT, account); data.putExtra("conversation", request.getStringExtra("conversation")); data.putExtra("multiple", false); @@ -124,6 +128,15 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { } + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + final Intent i = getIntent(); + boolean showEnterJid = i != null && i.getBooleanExtra("show_enter_jid", false); + menu.findItem(R.id.action_create_contact).setVisible(showEnterJid); + return true; + } + protected void filterContacts(final String needle) { getListItems().clear(); for (final Account account : xmppConnectionService.getAccounts()) { @@ -149,4 +162,62 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { return result.toArray(new String[result.size()]); } + + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_create_contact: + showEnterJidDialog(); + return true; + } + return super.onOptionsItemSelected(item); + } + + protected void showEnterJidDialog() { + EnterJidDialog dialog = new EnterJidDialog( + this, mKnownHosts, mActivatedAccounts, + getString(R.string.enter_contact), getString(R.string.select), + null, getIntent().getStringExtra(EXTRA_ACCOUNT), true + ); + + dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() { + @Override + public boolean onEnterJidDialogPositive(Jid accountJid, Jid contactJid) throws EnterJidDialog.JidError { + final Intent request = getIntent(); + final Intent data = new Intent(); + data.putExtra("contact", contactJid.toString()); + data.putExtra(EXTRA_ACCOUNT, accountJid.toString()); + data.putExtra("conversation", + request.getStringExtra("conversation")); + data.putExtra("multiple", false); + setResult(RESULT_OK, data); + finish(); + + return true; + } + }); + + dialog.show(); + } + + @Override + void onBackendConnected() { + filterContacts(); + + this.mActivatedAccounts.clear(); + for (Account account : xmppConnectionService.getAccounts()) { + if (account.getStatus() != Account.State.DISABLED) { + if (Config.DOMAIN_LOCK != null) { + this.mActivatedAccounts.add(account.getJid().getLocalpart()); + } else { + this.mActivatedAccounts.add(account.getJid().toBareJid().toString()); + } + } + } + this.mKnownHosts = xmppConnectionService.getKnownHosts(); + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConferenceDetailsActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConferenceDetailsActivity.java index 518041f2..c1b5ccc6 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ConferenceDetailsActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ConferenceDetailsActivity.java @@ -6,7 +6,6 @@ import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; import android.content.IntentSender.SendIntentException; -import android.graphics.Bitmap; import android.os.Build; import android.os.Bundle; import android.view.ContextMenu; @@ -19,6 +18,7 @@ import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.TableLayout; import android.widget.TextView; import android.widget.Toast; @@ -27,7 +27,9 @@ import org.openintents.openpgp.util.OpenPgpUtils; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.concurrent.atomic.AtomicInteger; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.crypto.PgpEngine; import de.thedevstack.conversationsplus.entities.Account; @@ -38,8 +40,8 @@ import de.thedevstack.conversationsplus.entities.MucOptions; import de.thedevstack.conversationsplus.entities.MucOptions.User; import de.thedevstack.conversationsplus.services.AvatarService; import de.thedevstack.conversationsplus.services.XmppConnectionService; -import de.thedevstack.conversationsplus.services.XmppConnectionService.OnMucRosterUpdate; import de.thedevstack.conversationsplus.services.XmppConnectionService.OnConversationUpdate; +import de.thedevstack.conversationsplus.services.XmppConnectionService.OnMucRosterUpdate; import de.thedevstack.conversationsplus.xmpp.jid.Jid; public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConferenceOptionsPushed { @@ -61,7 +63,11 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers private LinearLayout membersView; private LinearLayout mMoreDetails; private TextView mConferenceType; + private TableLayout mConferenceInfoTable; + private TextView mConferenceInfoMam; + private TextView mNotifyStatusText; private ImageButton mChangeConferenceSettingsButton; + private ImageButton mNotifyStatusButton; private Button mInviteButton; private String uuid = null; private User mSelectedUser = null; @@ -96,17 +102,76 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } }; + + private OnClickListener mNotifyStatusClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this); + builder.setTitle(R.string.pref_notification_settings); + String[] choices = { + getString(R.string.notify_on_all_messages), + getString(R.string.notify_only_when_highlighted), + getString(R.string.notify_never) + }; + final AtomicInteger choice; + if (mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0) == Long.MAX_VALUE) { + choice = new AtomicInteger(2); + } else { + choice = new AtomicInteger(mConversation.alwaysNotify() ? 0 : 1); + } + builder.setSingleChoiceItems(choices, choice.get(), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + choice.set(which); + } + }); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (choice.get() == 2) { + mConversation.setMutedTill(Long.MAX_VALUE); + } else { + mConversation.setMutedTill(0); + mConversation.setAttribute(Conversation.ATTRIBUTE_ALWAYS_NOTIFY,String.valueOf(choice.get() == 0)); + } + xmppConnectionService.updateConversation(mConversation); + updateView(); + } + }); + builder.create().show(); + } + }; + private OnClickListener mChangeConferenceSettings = new OnClickListener() { @Override public void onClick(View v) { final MucOptions mucOptions = mConversation.getMucOptions(); AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this); builder.setTitle(R.string.conference_options); - String[] options = {getString(R.string.members_only), - getString(R.string.non_anonymous)}; - final boolean[] values = new boolean[options.length]; - values[0] = mucOptions.membersOnly(); - values[1] = mucOptions.nonanonymous(); + final String[] options; + final boolean[] values; + if (mAdvancedMode) { + options = new String[]{ + getString(R.string.members_only), + getString(R.string.moderated), + getString(R.string.non_anonymous) + }; + values = new boolean[]{ + mucOptions.membersOnly(), + mucOptions.moderated(), + mucOptions.nonanonymous() + }; + } else { + options = new String[]{ + getString(R.string.members_only), + getString(R.string.non_anonymous) + }; + values = new boolean[]{ + mucOptions.membersOnly(), + mucOptions.nonanonymous() + }; + } builder.setMultiChoiceItems(options,values,new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { @@ -124,7 +189,12 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } Bundle options = new Bundle(); options.putString("muc#roomconfig_membersonly", values[0] ? "1" : "0"); - options.putString("muc#roomconfig_whois", values[1] ? "anyone" : "moderators"); + if (values.length == 2) { + options.putString("muc#roomconfig_whois", values[1] ? "anyone" : "moderators"); + } else if (values.length == 3) { + options.putString("muc#roomconfig_moderatedroom", values[1] ? "1" : "0"); + options.putString("muc#roomconfig_whois", values[2] ? "anyone" : "moderators"); + } options.putString("muc#roomconfig_persistentroom", "1"); xmppConnectionService.pushConferenceConfiguration(mConversation, options, @@ -171,7 +241,6 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers mMoreDetails.setVisibility(View.GONE); mChangeConferenceSettingsButton = (ImageButton) findViewById(R.id.change_conference_button); mChangeConferenceSettingsButton.setOnClickListener(this.mChangeConferenceSettings); - mConferenceType = (TextView) findViewById(R.id.muc_conference_type); mInviteButton = (Button) findViewById(R.id.invite); mInviteButton.setOnClickListener(inviteListener); mConferenceType = (TextView) findViewById(R.id.muc_conference_type); @@ -193,6 +262,13 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers }); } }); + this.mAdvancedMode = getPreferences().getBoolean("advanced_muc_mode", false); + this.mConferenceInfoTable = (TableLayout) findViewById(R.id.muc_info_more); + mConferenceInfoTable.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE); + this.mConferenceInfoMam = (TextView) findViewById(R.id.muc_info_mam); + this.mNotifyStatusButton = (ImageButton) findViewById(R.id.notification_status_button); + this.mNotifyStatusButton.setOnClickListener(this.mNotifyStatusClickListener); + this.mNotifyStatusText = (TextView) findViewById(R.id.notification_status_text); } @Override @@ -215,6 +291,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers case R.id.action_advanced_mode: this.mAdvancedMode = !menuItem.isChecked(); menuItem.setChecked(this.mAdvancedMode); + getPreferences().edit().putBoolean("advanced_muc_mode", mAdvancedMode).commit(); + mConferenceInfoTable.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE); invalidateOptionsMenu(); updateView(); break; @@ -236,6 +314,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark); MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark); MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode); + MenuItem menuItemChangeSubject = menu.findItem(R.id.action_edit_subject); menuItemAdvancedMode.setChecked(mAdvancedMode); if (mConversation == null) { return true; @@ -248,6 +327,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers menuItemDeleteBookmark.setVisible(false); menuItemSaveBookmark.setVisible(true); } + menuItemChangeSubject.setVisible(mConversation.getMucOptions().canChangeSubject()); return true; } @@ -266,14 +346,17 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers final User self = mConversation.getMucOptions().getSelf(); this.mSelectedUser = user; String name; + final Contact contact = user.getContact(); + if (contact != null) { + name = contact.getDisplayName(); + } else if (user.getJid() != null){ + name = user.getJid().toBareJid().toString(); + } else { + name = user.getName(); + } + menu.setHeaderTitle(name); if (user.getJid() != null) { - final Contact contact = user.getContact(); - if (contact != null) { - name = contact.getDisplayName(); - } else { - name = user.getJid().toBareJid().toString(); - } - menu.setHeaderTitle(name); + MenuItem showContactDetails = menu.findItem(R.id.action_contact_details); MenuItem startConversation = menu.findItem(R.id.start_conversation); MenuItem giveMembership = menu.findItem(R.id.give_membership); MenuItem removeMembership = menu.findItem(R.id.remove_membership); @@ -282,6 +365,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room); MenuItem banFromConference = menu.findItem(R.id.ban_from_conference); startConversation.setVisible(true); + if (contact != null) { + showContactDetails.setVisible(true); + } if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) && self.getAffiliation().outranks(user.getAffiliation())) { if (mAdvancedMode) { @@ -300,15 +386,24 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers removeAdminPrivileges.setVisible(true); } } + } else { + MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message); + sendPrivateMessage.setVisible(true); } } - super.onCreateContextMenu(menu,v,menuInfo); + super.onCreateContextMenu(menu, v, menuInfo); } @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { + case R.id.action_contact_details: + Contact contact = mSelectedUser.getContact(); + if (contact != null) { + switchToContactDetails(contact); + } + return true; case R.id.start_conversation: startConversation(mSelectedUser); return true; @@ -331,6 +426,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.OUTCAST,this); xmppConnectionService.changeRoleInConference(mConversation,mSelectedUser.getName(), MucOptions.Role.NONE,this); return true; + case R.id.send_private_message: + privateMsgInMuc(mConversation,mSelectedUser.getName()); + return true; default: return super.onContextItemSelected(item); } @@ -369,7 +467,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers if (!mConversation.getJid().isBareJid()) { bookmark.setNick(mConversation.getJid().getResourcepart()); } - bookmark.setAutojoin(true); + bookmark.setBookmarkName(mConversation.getMucOptions().getSubject()); + bookmark.setAutojoin(getPreferences().getBoolean("autojoin",true)); account.getBookmarks().add(bookmark); xmppConnectionService.pushBookmarks(account); mConversation.setBookmark(bookmark); @@ -404,11 +503,20 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers private void updateView() { final MucOptions mucOptions = mConversation.getMucOptions(); final User self = mucOptions.getSelf(); - mAccountJid.setText(getString(R.string.using_account, mConversation - .getAccount().getJid().toBareJid())); + String account; + if (Config.DOMAIN_LOCK != null) { + account = mConversation.getAccount().getJid().getLocalpart(); + } else { + account = mConversation.getAccount().getJid().toBareJid().toString(); + } + mAccountJid.setText(getString(R.string.using_account, account)); mYourPhoto.setImageBitmap(AvatarService.getInstance().get(mConversation.getAccount(), getPixel(48))); setTitle(mConversation.getName()); - mFullJid.setText(mConversation.getJid().toBareJid().toString()); + if (Config.LOCK_DOMAINS_IN_CONVERSATIONS && mConversation.getJid().getDomainpart().equals(Config.CONFERENCE_DOMAIN_LOCK)) { + mFullJid.setText(mConversation.getJid().getLocalpart()); + } else { + mFullJid.setText(mConversation.getJid().toBareJid().toString()); + } mYourNick.setText(mucOptions.getActualNick()); mRoleAffiliaton = (TextView) findViewById(R.id.muc_role); if (mucOptions.online()) { @@ -425,16 +533,36 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } else { mConferenceType.setText(R.string.public_conference); } + if (mucOptions.mamSupport()) { + mConferenceInfoMam.setText(R.string.server_info_available); + } else { + mConferenceInfoMam.setText(R.string.server_info_unavailable); + } if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) { mChangeConferenceSettingsButton.setVisibility(View.VISIBLE); } else { mChangeConferenceSettingsButton.setVisibility(View.GONE); } } + + long mutedTill = mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0); + if (mutedTill == Long.MAX_VALUE) { + mNotifyStatusText.setText(R.string.notify_never); + mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_off_grey600_24dp); + } else if (System.currentTimeMillis() < mutedTill) { + mNotifyStatusText.setText(R.string.notify_paused); + mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_paused_grey600_24dp); + } else if (mConversation.alwaysNotify()) { + mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_grey600_24dp); + mNotifyStatusText.setText(R.string.notify_on_all_messages); + } else { + mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_none_grey600_24dp); + mNotifyStatusText.setText(R.string.notify_only_when_highlighted); + } + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); membersView.removeAllViews(); - final ArrayList<User> users = new ArrayList<>(); - users.addAll(mConversation.getMucOptions().getUsers()); + final ArrayList<User> users = mucOptions.getUsers(); Collections.sort(users,new Comparator<User>() { @Override public int compare(User lhs, User rhs) { @@ -466,20 +594,17 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers }); tvKey.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId())); } - Bitmap bm; Contact contact = user.getContact(); if (contact != null) { - bm = AvatarService.getInstance().get(contact, getPixel(48)); tvDisplayName.setText(contact.getDisplayName()); tvStatus.setText(user.getName() + " \u2022 " + getStatus(user)); } else { - bm = AvatarService.getInstance().get(user.getName(), getPixel(48)); tvDisplayName.setText(user.getName()); tvStatus.setText(getStatus(user)); } ImageView iv = (ImageView) view.findViewById(R.id.contact_photo); - iv.setImageBitmap(bm); + iv.setImageBitmap(AvatarService.getInstance().get(user, getPixel(48), false)); membersView.addView(view); if (mConversation.getMucOptions().canInvite()) { mInviteButton.setVisibility(View.VISIBLE); diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ContactDetailsActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ContactDetailsActivity.java index a61d0b4b..aaa19fc5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ContactDetailsActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ContactDetailsActivity.java @@ -25,29 +25,35 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.QuickContactBadge; import android.widget.TextView; +import android.widget.Toast; import org.openintents.openpgp.util.OpenPgpUtils; +import java.security.cert.X509Certificate; import java.util.List; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.crypto.PgpEngine; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; +import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.ListItem; import de.thedevstack.conversationsplus.services.AvatarService; import de.thedevstack.conversationsplus.services.XmppConnectionService.OnAccountUpdate; import de.thedevstack.conversationsplus.services.XmppConnectionService.OnRosterUpdate; -import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener; import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.UIHelper; +import de.thedevstack.conversationsplus.xmpp.OnKeyStatusUpdated; import de.thedevstack.conversationsplus.xmpp.OnUpdateBlocklist; import de.thedevstack.conversationsplus.xmpp.XmppConnection; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; -public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist { +public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated { public static final String ACTION_VIEW_CONTACT = "view_contact"; private Contact contact; @@ -109,6 +115,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd private LinearLayout keys; private LinearLayout tags; private boolean showDynamicTags; + private String messageFingerprint; private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() { @@ -133,7 +140,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd ContactDetailsActivity.this); builder.setTitle(getString(R.string.action_add_phone_book)); builder.setMessage(getString(R.string.add_phone_book_text, - contact.getJid())); + contact.getDisplayJid())); builder.setNegativeButton(getString(R.string.cancel), null); builder.setPositiveButton(getString(R.string.add), addToPhonebook); builder.create().show(); @@ -159,6 +166,11 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } @Override + public void OnUpdateBlocklist(final Status status) { + refreshUi(); + } + + @Override protected void refreshUiReal() { invalidateOptionsMenu(); populateView(); @@ -178,7 +190,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd super.onCreate(savedInstanceState); if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) { try { - this.accountJid = Jid.fromString(getIntent().getExtras().getString("account")); + this.accountJid = Jid.fromString(getIntent().getExtras().getString(EXTRA_ACCOUNT)); } catch (final InvalidJidException ignored) { } try { @@ -186,6 +198,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } catch (final InvalidJidException ignored) { } } + this.messageFingerprint = getIntent().getStringExtra("fingerprint"); setContentView(R.layout.activity_contact_details); contactJidTv = (TextView) findViewById(R.id.details_contactjid); @@ -223,7 +236,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd builder.setTitle(getString(R.string.action_delete_contact)) .setMessage( getString(R.string.remove_contact_text, - contact.getJid())) + contact.getDisplayJid())) .setPositiveButton(getString(R.string.delete), removeFromRoster).create().show(); break; @@ -345,13 +358,19 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } if (contact.getPresences().size() > 1) { - contactJidTv.setText(contact.getJid() + " (" + contactJidTv.setText(contact.getDisplayJid() + " (" + contact.getPresences().size() + ")"); } else { - contactJidTv.setText(contact.getJid().toString()); + contactJidTv.setText(contact.getDisplayJid()); + } + String account; + if (Config.DOMAIN_LOCK != null) { + account = contact.getAccount().getJid().getLocalpart(); + } else { + account = contact.getAccount().getJid().toBareJid().toString(); } contactJidTv.setOnClickListener(new ShowResourcesListDialogListener(ContactDetailsActivity.this, contact)); - accountJidTv.setText(getString(R.string.using_account, contact.getAccount().getJid().toBareJid())); + accountJidTv.setText(getString(R.string.using_account, account)); badge.setImageBitmap(AvatarService.getInstance().get(contact, getPixel(72))); badge.setOnClickListener(this.onBadgeClick); @@ -363,13 +382,13 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd 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 + ImageButton removeButton = (ImageButton) view .findViewById(R.id.button_remove); - remove.setVisibility(View.VISIBLE); + removeButton.setVisibility(View.VISIBLE); keyType.setText("OTR Fingerprint"); key.setText(CryptoHelper.prettifyFingerprint(otrFingerprint)); keys.addView(view); - remove.setOnClickListener(new OnClickListener() { + removeButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -377,6 +396,15 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } }); } + for (final String fingerprint : contact.getAccount().getAxolotlService().getFingerprintsForContact(contact)) { + boolean highlight = fingerprint.equals(messageFingerprint); + hasKeys |= addFingerprintRow(keys, contact.getAccount(), fingerprint, highlight, new OnClickListener() { + @Override + public void onClick(View v) { + onOmemoKeyClicked(contact.getAccount(), fingerprint); + } + }); + } if (contact.getPgpKeyId() != 0) { hasKeys = true; View view = inflater.inflate(R.layout.contact_key, keys, false); @@ -427,6 +455,40 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } } + private void onOmemoKeyClicked(Account account, String fingerprint) { + final XmppAxolotlSession.Trust trust = account.getAxolotlService().getFingerprintTrust(fingerprint); + if (trust != null && trust == XmppAxolotlSession.Trust.TRUSTED_X509) { + X509Certificate x509Certificate = account.getAxolotlService().getFingerprintCertificate(fingerprint); + if (x509Certificate != null) { + showCertificateInformationDialog(CryptoHelper.extractCertificateInformation(x509Certificate)); + } else { + Toast.makeText(this,R.string.certificate_not_found, Toast.LENGTH_SHORT).show(); + } + } + } + + private void showCertificateInformationDialog(Bundle bundle) { + View view = getLayoutInflater().inflate(R.layout.certificate_information, null); + final String not_available = getString(R.string.certicate_info_not_available); + TextView subject_cn = (TextView) view.findViewById(R.id.subject_cn); + TextView subject_o = (TextView) view.findViewById(R.id.subject_o); + TextView issuer_cn = (TextView) view.findViewById(R.id.issuer_cn); + TextView issuer_o = (TextView) view.findViewById(R.id.issuer_o); + TextView sha1 = (TextView) view.findViewById(R.id.sha1); + + subject_cn.setText(bundle.getString("subject_cn", not_available)); + subject_o.setText(bundle.getString("subject_o", not_available)); + issuer_cn.setText(bundle.getString("issuer_cn", not_available)); + issuer_o.setText(bundle.getString("issuer_o", not_available)); + sha1.setText(bundle.getString("sha1", not_available)); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.certificate_information); + builder.setView(view); + builder.setPositiveButton(R.string.ok, null); + builder.create().show(); + } + protected void confirmToDeleteFingerprint(final String fingerprint) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.delete_fingerprint); @@ -461,14 +523,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } @Override - public void OnUpdateBlocklist(final Status status) { - runOnUiThread(new Runnable() { - - @Override - public void run() { - invalidateOptionsMenu(); - populateView(); - } - }); + public void onKeyStatusUpdated(AxolotlService.FetchStatus report) { + refreshUi(); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java index 94d1e2ea..1a4ccb8c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java @@ -10,14 +10,21 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentSender.SendIntentException; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.MediaStore; +import android.provider.Settings; import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener; +import android.util.Log; +import android.util.Pair; +import android.view.Gravity; +import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; +import android.view.Surface; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; @@ -29,22 +36,29 @@ import android.widget.Toast; import net.java.otr4j.session.SessionStatus; -import de.thedevstack.conversationsplus.ConversationsPlusPreferences; -import de.thedevstack.conversationsplus.persistance.FileBackend; -import de.thedevstack.conversationsplus.ui.dialogs.UserDecisionDialog; -import de.thedevstack.conversationsplus.ui.listeners.ResizePictureUserDecisionListener; -import de.timroes.android.listview.EnhancedListView; +import org.openintents.openpgp.util.OpenPgpApi; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.ui.dialogs.UserDecisionDialog; +import de.thedevstack.conversationsplus.ui.listeners.ResizePictureUserDecisionListener; +import de.timroes.android.listview.EnhancedListView; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlServiceImpl; +import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Blockable; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.Transferable; +import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.services.XmppConnectionService.OnAccountUpdate; import de.thedevstack.conversationsplus.services.XmppConnectionService.OnConversationUpdate; @@ -52,26 +66,33 @@ import de.thedevstack.conversationsplus.services.XmppConnectionService.OnRosterU import de.thedevstack.conversationsplus.ui.adapter.ConversationAdapter; import de.thedevstack.conversationsplus.utils.ExceptionHelper; import de.thedevstack.conversationsplus.xmpp.OnUpdateBlocklist; +import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; public class ConversationActivity extends XmppActivity implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast { - public static final String ACTION_DOWNLOAD = "de.thedevstack.conversationsplus.action.DOWNLOAD"; + public static final String ACTION_DOWNLOAD = "eu.siacs.conversations.action.DOWNLOAD"; public static final String VIEW_CONVERSATION = "viewConversation"; public static final String CONVERSATION = "conversationUuid"; public static final String MESSAGE = "messageUuid"; public static final String TEXT = "text"; public static final String NICK = "nick"; + public static final String PRIVATE_MESSAGE = "pm"; public static final int REQUEST_SEND_MESSAGE = 0x0201; public static final int REQUEST_DECRYPT_PGP = 0x0202; public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207; + public static final int REQUEST_TRUST_KEYS_TEXT = 0x0208; + public static final int REQUEST_TRUST_KEYS_MENU = 0x0209; + public static final int REQUEST_START_DOWNLOAD = 0x0210; public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301; public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302; public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303; public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304; public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305; + public static final int ATTACHMENT_CHOICE_INVALID = 0x0306; private static final String STATE_OPEN_CONVERSATION = "state_open_conversation"; private static final String STATE_PANEL_OPEN = "state_panel_open"; private static final String STATE_PENDING_URI = "state_pending_uri"; @@ -81,6 +102,10 @@ public class ConversationActivity extends XmppActivity final private List<Uri> mPendingImageUris = new ArrayList<>(); final private List<Uri> mPendingFileUris = new ArrayList<>(); private Uri mPendingGeoUri = null; + private boolean forbidProcessingPendings = false; + private Message mPendingDownloadableMessage = null; + + private boolean conversationWasSelectedByKeyboard = false; private View mContentView; @@ -92,10 +117,9 @@ public class ConversationActivity extends XmppActivity private ArrayAdapter<Conversation> listAdapter; - private Toast prepareFileToast; - private boolean mActivityPaused = false; - private boolean mRedirected = true; + private AtomicBoolean mRedirected = new AtomicBoolean(false); + private Pair<Integer, Intent> mPostponedActivityResult; public Conversation getSelectedConversation() { return this.mSelectedConversation; @@ -131,8 +155,7 @@ public class ConversationActivity extends XmppActivity public boolean isConversationsOverviewHideable() { if (mContentView instanceof SlidingPaneLayout) { - SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView; - return mSlidingPaneLayout.isSlideable(); + return true; } else { return false; } @@ -180,10 +203,11 @@ public class ConversationActivity extends XmppActivity @Override public void onItemClick(AdapterView<?> arg0, View clickedView, - int position, long arg3) { + int position, long arg3) { if (getSelectedConversation() != conversationList.get(position)) { setSelectedConversation(conversationList.get(position)); ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); + conversationWasSelectedByKeyboard = false; } hideConversationsOverview(); openConversation(); @@ -192,64 +216,67 @@ public class ConversationActivity extends XmppActivity listView.setDismissCallback(new EnhancedListView.OnDismissCallback() { - @Override - public EnhancedListView.Undoable onDismiss(final EnhancedListView enhancedListView, final int position) { - - final int index = listView.getFirstVisiblePosition(); - View v = listView.getChildAt(0); - final int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop()); - - swipedConversation = listAdapter.getItem(position); - listAdapter.remove(swipedConversation); - swipedConversation.markRead(); - xmppConnectionService.getNotificationService().clear(swipedConversation); - - final boolean formerlySelected = (getSelectedConversation() == swipedConversation); - if (position == 0 && listAdapter.getCount() == 0) { - endConversation(swipedConversation, false, true); - return null; - } else if (formerlySelected) { - setSelectedConversation(listAdapter.getItem(0)); - ConversationActivity.this.mConversationFragment - .reInit(getSelectedConversation()); - } + @Override + public EnhancedListView.Undoable onDismiss(final EnhancedListView enhancedListView, final int position) { - return new EnhancedListView.Undoable() { + final int index = listView.getFirstVisiblePosition(); + View v = listView.getChildAt(0); + final int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop()); - @Override - public void undo() { - listAdapter.insert(swipedConversation, position); - if (formerlySelected) { - setSelectedConversation(swipedConversation); - ConversationActivity.this.mConversationFragment - .reInit(getSelectedConversation()); - } - swipedConversation = null; - listView.setSelectionFromTop(index + (listView.getChildCount() < position ? 1 : 0), top); - } + try { + swipedConversation = listAdapter.getItem(position); + } catch (IndexOutOfBoundsException e) { + return null; + } + listAdapter.remove(swipedConversation); + xmppConnectionService.markRead(swipedConversation); + + final boolean formerlySelected = (getSelectedConversation() == swipedConversation); + if (position == 0 && listAdapter.getCount() == 0) { + endConversation(swipedConversation, false, true); + return null; + } else if (formerlySelected) { + setSelectedConversation(listAdapter.getItem(0)); + ConversationActivity.this.mConversationFragment + .reInit(getSelectedConversation()); + } - @Override - public void discard() { - if (!swipedConversation.isRead() - && swipedConversation.getMode() == Conversation.MODE_SINGLE) { - swipedConversation = null; - return; - } - endConversation(swipedConversation, false, false); - swipedConversation = null; - } + return new EnhancedListView.Undoable() { - @Override - public String getTitle() { - if (swipedConversation.getMode() == Conversation.MODE_MULTI) { - return getResources().getString(R.string.title_undo_swipe_out_muc); - } else { - return getResources().getString(R.string.title_undo_swipe_out_conversation); - } - } - }; - } - }); + @Override + public void undo() { + listAdapter.insert(swipedConversation, position); + if (formerlySelected) { + setSelectedConversation(swipedConversation); + ConversationActivity.this.mConversationFragment + .reInit(getSelectedConversation()); + } + swipedConversation = null; + listView.setSelectionFromTop(index + (listView.getChildCount() < position ? 1 : 0), top); + } + + @Override + public void discard() { + if (!swipedConversation.isRead() + && swipedConversation.getMode() == Conversation.MODE_SINGLE) { + swipedConversation = null; + return; + } + endConversation(swipedConversation, false, false); + swipedConversation = null; + } + + @Override + public String getTitle() { + if (swipedConversation.getMode() == Conversation.MODE_MULTI) { + return getResources().getString(R.string.title_undo_swipe_out_muc); + } else { + return getResources().getString(R.string.title_undo_swipe_out_conversation); + } + } + }; + } + }); listView.enableSwipeToDismiss(); listView.setSwipeDirection(EnhancedListView.SwipeDirection.START); listView.setSwipingLayout(R.id.swipeable_item); @@ -277,7 +304,7 @@ public class ConversationActivity extends XmppActivity hideKeyboard(); if (xmppConnectionServiceBound) { xmppConnectionService.getNotificationService() - .setOpenConversation(null); + .setOpenConversation(null); } closeContextMenu(); } @@ -301,12 +328,12 @@ public class ConversationActivity extends XmppActivity public void switchToConversation(Conversation conversation) { setSelectedConversation(conversation); runOnUiThread(new Runnable() { - @Override - public void run() { - ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); - openConversation(); - } - }); + @Override + public void run() { + ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); + openConversation(); + } + }); } private void updateActionBarTitle() { @@ -346,11 +373,7 @@ public class ConversationActivity extends XmppActivity public void sendReadMarkerIfNecessary(final Conversation conversation) { if (!mActivityPaused && conversation != null) { - if (!conversation.isRead()) { - xmppConnectionService.sendReadMarker(conversation); - } else { - xmppConnectionService.markRead(conversation); - } + xmppConnectionService.sendReadMarker(conversation); } } @@ -381,7 +404,7 @@ public class ConversationActivity extends XmppActivity } else { menuAdd.setVisible(!isConversationsOverviewHideable()); if (this.getSelectedConversation() != null) { - if (this.getSelectedConversation().getNextEncryption(ConversationsPlusPreferences.forceEncryption()) != Message.ENCRYPTION_NONE) { + if (this.getSelectedConversation().getNextEncryption() != Message.ENCRYPTION_NONE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { menuSecure.setIcon(R.drawable.ic_lock_white_24dp); } else { @@ -390,10 +413,12 @@ public class ConversationActivity extends XmppActivity } if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) { menuContactDetails.setVisible(false); - menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable()); + menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable() && getSelectedConversation().getMucOptions().participating()); menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite()); + menuSecure.setVisible(Config.supportOpenPgp() && Config.multipleEncryptionChoices()); //only if pgp is supported we have a choice } else { menuMucDetails.setVisible(false); + menuSecure.setVisible(Config.multipleEncryptionChoices()); } if (this.getSelectedConversation().isMuted()) { menuMute.setVisible(false); @@ -405,7 +430,7 @@ public class ConversationActivity extends XmppActivity return true; } - private void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) { + protected void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) { final Conversation conversation = getSelectedConversation(); final Account account = conversation.getAccount(); final OnPresenceSelected callback = new OnPresenceSelected() { @@ -419,7 +444,7 @@ public class ConversationActivity extends XmppActivity case ATTACHMENT_CHOICE_CHOOSE_IMAGE: intent.setAction(Intent.ACTION_GET_CONTENT); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } intent.setType("image/*"); chooser = true; @@ -439,11 +464,11 @@ public class ConversationActivity extends XmppActivity break; case ATTACHMENT_CHOICE_RECORD_VOICE: intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION); - fallbackPackageId = "de.thedevstack.conversationsplus.voicerecorder"; + fallbackPackageId = "eu.siacs.conversations.voicerecorder"; break; case ATTACHMENT_CHOICE_LOCATION: - intent.setAction("de.thedevstack.conversationsplus.location.request"); - fallbackPackageId = "de.thedevstack.conversationsplus.sharelocation"; + intent.setAction("eu.siacs.conversations.location.request"); + fallbackPackageId = "eu.siacs.conversations.sharelocation"; break; } if (intent.resolveActivity(getPackageManager()) != null) { @@ -469,7 +494,7 @@ public class ConversationActivity extends XmppActivity private Intent getInstallApkIntent(final String packageId) { Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse("market://details?id="+packageId)); + intent.setData(Uri.parse("market://details?id=" + packageId)); if (intent.resolveActivity(getPackageManager()) != null) { return intent; } else { @@ -479,6 +504,11 @@ public class ConversationActivity extends XmppActivity } public void attachFile(final int attachmentChoice) { + if (attachmentChoice != ATTACHMENT_CHOICE_LOCATION) { + if (!hasStoragePermission(attachmentChoice)) { + return; + } + } switch (attachmentChoice) { case ATTACHMENT_CHOICE_LOCATION: ConversationsPlusPreferences.applyRecentlyUsedQuickAction("location"); @@ -494,23 +524,23 @@ public class ConversationActivity extends XmppActivity break; } final Conversation conversation = getSelectedConversation(); - final int encryption = conversation.getNextEncryption(ConversationsPlusPreferences.forceEncryption()); + final int encryption = conversation.getNextEncryption(); + final int mode = conversation.getMode(); if (encryption == Message.ENCRYPTION_PGP) { if (hasPgp()) { - if (conversation.getContact().getPgpKeyId() != 0) { + if (mode == Conversation.MODE_SINGLE && 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); + public void userInputRequried(PendingIntent pi, Contact contact) { + ConversationActivity.this.runIntent(pi, attachmentChoice); } @Override public void success(Contact contact) { - selectPresenceToAttachFile(attachmentChoice,encryption); + selectPresenceToAttachFile(attachmentChoice, encryption); } @Override @@ -518,21 +548,31 @@ public class ConversationActivity extends XmppActivity displayErrorDialog(error); } }); + } else if (mode == Conversation.MODE_MULTI && conversation.getMucOptions().pgpKeysInUse()) { + if (!conversation.getMucOptions().everybodyHasKeys()) { + Toast warning = Toast + .makeText(this, + R.string.missing_public_keys, + Toast.LENGTH_LONG); + warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); + warning.show(); + } + selectPresenceToAttachFile(attachmentChoice, encryption); } else { final ConversationFragment fragment = (ConversationFragment) getFragmentManager() - .findFragmentByTag("conversation"); + .findFragmentByTag("conversation"); if (fragment != null) { fragment.showNoPGPKeyDialog(false, new OnClickListener() { @Override public void onClick(DialogInterface dialog, - int which) { + int which) { conversation - .setNextEncryption(Message.ENCRYPTION_NONE); + .setNextEncryption(Message.ENCRYPTION_NONE); xmppConnectionService.databaseBackend - .updateConversation(conversation); - selectPresenceToAttachFile(attachmentChoice,Message.ENCRYPTION_NONE); + .updateConversation(conversation); + selectPresenceToAttachFile(attachmentChoice, Message.ENCRYPTION_NONE); } }); } @@ -541,7 +581,40 @@ public class ConversationActivity extends XmppActivity showInstallPgpDialog(); } } else { - selectPresenceToAttachFile(attachmentChoice, encryption); + if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) { + selectPresenceToAttachFile(attachmentChoice, encryption); + } + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + if (grantResults.length > 0) + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == REQUEST_START_DOWNLOAD) { + if (this.mPendingDownloadableMessage != null) { + startDownloadable(this.mPendingDownloadableMessage); + } + } else { + attachFile(requestCode); + } + } else { + Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); + } + } + + public void startDownloadable(Message message) { + if (!hasStoragePermission(ConversationActivity.REQUEST_START_DOWNLOAD)) { + this.mPendingDownloadableMessage = message; + return; + } + Transferable transferable = message.getTransferable(); + if (transferable != null) { + if (!transferable.start()) { + Toast.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show(); + } + } else if (message.treatAsDownloadable() != Message.Decision.NEVER) { + xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message, true); } } @@ -616,6 +689,12 @@ public class ConversationActivity extends XmppActivity this.mConversationFragment.reInit(getSelectedConversation()); } else { setSelectedConversation(null); + if (mRedirected.compareAndSet(false, true)) { + Intent intent = new Intent(this, StartConversationActivity.class); + intent.putExtra("init", true); + startActivity(intent); + finish(); + } } } } @@ -627,23 +706,23 @@ public class ConversationActivity extends XmppActivity View dialogView = getLayoutInflater().inflate( R.layout.dialog_clear_history, null); final CheckBox endConversationCheckBox = (CheckBox) dialogView - .findViewById(R.id.end_conversation_checkbox); + .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() { + new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - ConversationActivity.this.xmppConnectionService.clearConversationHistory(conversation); - if (endConversationCheckBox.isChecked()) { - endConversation(conversation); - } else { - updateConversationList(); - ConversationActivity.this.mConversationFragment.updateMessages(); - } - } - }); + @Override + public void onClick(DialogInterface dialog, int which) { + ConversationActivity.this.xmppConnectionService.clearConversationHistory(conversation); + if (endConversationCheckBox.isChecked()) { + endConversation(conversation); + } else { + updateConversationList(); + ConversationActivity.this.mConversationFragment.updateMessages(); + } + } + }); builder.create().show(); } @@ -657,7 +736,7 @@ public class ConversationActivity extends XmppActivity if (new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION).resolveActivity(getPackageManager()) == null) { attachFilePopup.getMenu().findItem(R.id.attach_record_voice).setVisible(false); } - if (new Intent("de.thedevstack.conversationsplus.location.request").resolveActivity(getPackageManager()) == null) { + if (new Intent("eu.siacs.conversations.location.request").resolveActivity(getPackageManager()) == null) { attachFilePopup.getMenu().findItem(R.id.attach_location).setVisible(false); } attachFilePopup.setOnMenuItemClickListener(new OnMenuItemClickListener() { @@ -703,7 +782,7 @@ public class ConversationActivity extends XmppActivity Intent intent = new Intent(ConversationActivity.this, VerifyOTRActivity.class); intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); - intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString()); + intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); switch (menuItem.getItemId()) { case R.id.scan_fingerprint: intent.putExtra("mode", VerifyOTRActivity.MODE_SCAN_FINGERPRINT); @@ -729,7 +808,7 @@ public class ConversationActivity extends XmppActivity } PopupMenu popup = new PopupMenu(this, menuItemView); final ConversationFragment fragment = (ConversationFragment) getFragmentManager() - .findFragmentByTag("conversation"); + .findFragmentByTag("conversation"); if (fragment != null) { popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { @@ -746,16 +825,22 @@ public class ConversationActivity extends XmppActivity break; case R.id.encryption_choice_pgp: if (hasPgp()) { - if (conversation.getAccount().getKeys().has("pgp_signature")) { + if (conversation.getAccount().getPgpSignature() != null) { conversation.setNextEncryption(Message.ENCRYPTION_PGP); item.setChecked(true); } else { - announcePgp(conversation.getAccount(),conversation); + announcePgp(conversation.getAccount(), conversation); } } else { showInstallPgpDialog(); } break; + case R.id.encryption_choice_axolotl: + Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(conversation.getAccount()) + + "Enabled axolotl for Contact " + conversation.getContact().getJid()); + conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); + item.setChecked(true); + break; default: conversation.setNextEncryption(Message.ENCRYPTION_NONE); break; @@ -763,6 +848,7 @@ public class ConversationActivity extends XmppActivity xmppConnectionService.databaseBackend.updateConversation(conversation); fragment.updateChatMsgHint(); invalidateOptionsMenu(); + refreshUi(); return true; } }); @@ -770,15 +856,18 @@ public class ConversationActivity extends XmppActivity MenuItem otr = popup.getMenu().findItem(R.id.encryption_choice_otr); MenuItem none = popup.getMenu().findItem(R.id.encryption_choice_none); MenuItem pgp = popup.getMenu().findItem(R.id.encryption_choice_pgp); - boolean forceEncryption = ConversationsPlusPreferences.forceEncryption(); + MenuItem axolotl = popup.getMenu().findItem(R.id.encryption_choice_axolotl); + pgp.setVisible(Config.supportOpenPgp()); + none.setVisible(Config.supportUnencrypted() || conversation.getMode() == Conversation.MODE_MULTI); + otr.setVisible(Config.supportOtr()); + axolotl.setVisible(Config.supportOmemo()); if (conversation.getMode() == Conversation.MODE_MULTI) { - otr.setEnabled(false); - } else { - if (forceEncryption) { - none.setVisible(false); - } + otr.setVisible(false); + axolotl.setVisible(false); + } else if (!conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) { + axolotl.setEnabled(false); } - switch (conversation.getNextEncryption(forceEncryption)) { + switch (conversation.getNextEncryption()) { case Message.ENCRYPTION_NONE: none.setChecked(true); break; @@ -788,6 +877,9 @@ public class ConversationActivity extends XmppActivity case Message.ENCRYPTION_PGP: pgp.setChecked(true); break; + case Message.ENCRYPTION_AXOLOTL: + axolotl.setChecked(true); + break; default: none.setChecked(true); break; @@ -799,27 +891,26 @@ public class ConversationActivity extends XmppActivity protected void muteConversationDialog(final Conversation conversation) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.disable_notifications); - final int[] durations = getResources().getIntArray( - R.array.mute_options_durations); + final int[] durations = getResources().getIntArray(R.array.mute_options_durations); builder.setItems(R.array.mute_options_descriptions, - new OnClickListener() { + new OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, final int which) { - final long till; - if (durations[which] == -1) { - till = Long.MAX_VALUE; - } else { - till = System.currentTimeMillis() + (durations[which] * 1000); - } - conversation.setMutedTill(till); - ConversationActivity.this.xmppConnectionService.databaseBackend - .updateConversation(conversation); - updateConversationList(); - ConversationActivity.this.mConversationFragment.updateMessages(); - invalidateOptionsMenu(); - } - }); + @Override + public void onClick(final DialogInterface dialog, final int which) { + final long till; + if (durations[which] == -1) { + till = Long.MAX_VALUE; + } else { + till = System.currentTimeMillis() + (durations[which] * 1000); + } + conversation.setMutedTill(till); + ConversationActivity.this.xmppConnectionService.databaseBackend + .updateConversation(conversation); + updateConversationList(); + ConversationActivity.this.mConversationFragment.updateMessages(); + invalidateOptionsMenu(); + } + }); builder.create().show(); } @@ -841,10 +932,119 @@ public class ConversationActivity extends XmppActivity } @Override + public boolean onKeyUp(int key, KeyEvent event) { + int rotation = getWindowManager().getDefaultDisplay().getRotation(); + final int upKey; + final int downKey; + switch (rotation) { + case Surface.ROTATION_90: + upKey = KeyEvent.KEYCODE_DPAD_LEFT; + downKey = KeyEvent.KEYCODE_DPAD_RIGHT; + break; + case Surface.ROTATION_180: + upKey = KeyEvent.KEYCODE_DPAD_DOWN; + downKey = KeyEvent.KEYCODE_DPAD_UP; + break; + case Surface.ROTATION_270: + upKey = KeyEvent.KEYCODE_DPAD_RIGHT; + downKey = KeyEvent.KEYCODE_DPAD_LEFT; + break; + default: + upKey = KeyEvent.KEYCODE_DPAD_UP; + downKey = KeyEvent.KEYCODE_DPAD_DOWN; + } + final boolean modifier = event.isCtrlPressed() || event.isAltPressed(); + if (modifier && key == KeyEvent.KEYCODE_TAB && isConversationsOverviewHideable()) { + toggleConversationsOverview(); + return true; + } else if (modifier && key == downKey) { + if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) { + showConversationsOverview(); + ; + } + return selectDownConversation(); + } else if (modifier && key == upKey) { + if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) { + showConversationsOverview(); + } + return selectUpConversation(); + } else if (modifier && key == KeyEvent.KEYCODE_1) { + return openConversationByIndex(0); + } else if (modifier && key == KeyEvent.KEYCODE_2) { + return openConversationByIndex(1); + } else if (modifier && key == KeyEvent.KEYCODE_3) { + return openConversationByIndex(2); + } else if (modifier && key == KeyEvent.KEYCODE_4) { + return openConversationByIndex(3); + } else if (modifier && key == KeyEvent.KEYCODE_5) { + return openConversationByIndex(4); + } else if (modifier && key == KeyEvent.KEYCODE_6) { + return openConversationByIndex(5); + } else if (modifier && key == KeyEvent.KEYCODE_7) { + return openConversationByIndex(6); + } else if (modifier && key == KeyEvent.KEYCODE_8) { + return openConversationByIndex(7); + } else if (modifier && key == KeyEvent.KEYCODE_9) { + return openConversationByIndex(8); + } else if (modifier && key == KeyEvent.KEYCODE_0) { + return openConversationByIndex(9); + } else { + return super.onKeyUp(key, event); + } + } + + private void toggleConversationsOverview() { + if (isConversationsOverviewVisable()) { + hideConversationsOverview(); + if (mConversationFragment != null) { + mConversationFragment.setFocusOnInputField(); + } + } else { + showConversationsOverview(); + } + } + + private boolean selectUpConversation() { + if (this.mSelectedConversation != null) { + int index = this.conversationList.indexOf(this.mSelectedConversation); + if (index > 0) { + return openConversationByIndex(index - 1); + } + } + return false; + } + + private boolean selectDownConversation() { + if (this.mSelectedConversation != null) { + int index = this.conversationList.indexOf(this.mSelectedConversation); + if (index != -1 && index < this.conversationList.size() - 1) { + return openConversationByIndex(index + 1); + } + } + return false; + } + + private boolean openConversationByIndex(int index) { + try { + this.conversationWasSelectedByKeyboard = true; + setSelectedConversation(this.conversationList.get(index)); + this.mConversationFragment.reInit(getSelectedConversation()); + if (index > listView.getLastVisiblePosition() - 1 || index < listView.getFirstVisiblePosition() + 1) { + this.listView.setSelection(index); + } + openConversation(); + return true; + } catch (IndexOutOfBoundsException e) { + return false; + } + } + + @Override protected void onNewIntent(final Intent intent) { if (xmppConnectionServiceBound) { if (intent != null && VIEW_CONVERSATION.equals(intent.getType())) { handleViewConversationIntent(intent); + setIntent(new Intent()); } } else { setIntent(intent); @@ -854,7 +1054,7 @@ public class ConversationActivity extends XmppActivity @Override public void onStart() { super.onStart(); - this.mRedirected = false; + this.mRedirected.set(false); if (this.xmppConnectionServiceBound) { this.onBackendConnected(); } @@ -896,17 +1096,26 @@ public class ConversationActivity extends XmppActivity public void onSaveInstanceState(final Bundle savedInstanceState) { Conversation conversation = getSelectedConversation(); if (conversation != null) { - savedInstanceState.putString(STATE_OPEN_CONVERSATION, - conversation.getUuid()); + savedInstanceState.putString(STATE_OPEN_CONVERSATION, conversation.getUuid()); + } else { + savedInstanceState.remove(STATE_OPEN_CONVERSATION); } - savedInstanceState.putBoolean(STATE_PANEL_OPEN, - isConversationsOverviewVisable()); + savedInstanceState.putBoolean(STATE_PANEL_OPEN, isConversationsOverviewVisable()); if (this.mPendingImageUris.size() >= 1) { savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUris.get(0).toString()); + } else { + savedInstanceState.remove(STATE_PENDING_URI); } super.onSaveInstanceState(savedInstanceState); } + private void clearPending() { + mPendingImageUris.clear(); + mPendingFileUris.clear(); + mPendingGeoUri = null; + mPostponedActivityResult = null; + } + @Override void onBackendConnected() { this.xmppConnectionService.getNotificationService().setIsInForeground(true); @@ -918,20 +1127,23 @@ public class ConversationActivity extends XmppActivity } if (xmppConnectionService.getAccounts().size() == 0) { - if (!mRedirected) { - this.mRedirected = true; - startActivity(new Intent(this, EditAccountActivity.class)); + if (mRedirected.compareAndSet(false, true)) { + if (Config.X509_VERIFICATION) { + startActivity(new Intent(this, ManageAccountActivity.class)); + } else { + startActivity(new Intent(this, EditAccountActivity.class)); + } finish(); } } else if (conversationList.size() <= 0) { - if (!mRedirected) { - this.mRedirected = true; + if (mRedirected.compareAndSet(false, true)) { Intent intent = new Intent(this, StartConversationActivity.class); - intent.putExtra("init",true); + intent.putExtra("init", true); startActivity(intent); finish(); } } else if (getIntent() != null && VIEW_CONVERSATION.equals(getIntent().getType())) { + clearPending(); handleViewConversationIntent(getIntent()); } else if (selectConversationByUuid(mOpenConverstaion)) { if (mPanelOpen) { @@ -939,32 +1151,45 @@ public class ConversationActivity extends XmppActivity } else { if (isConversationsOverviewHideable()) { openConversation(); + updateActionBarTitle(true); } } this.mConversationFragment.reInit(getSelectedConversation()); mOpenConverstaion = null; } else if (getSelectedConversation() == null) { showConversationsOverview(); - mPendingImageUris.clear(); - mPendingFileUris.clear(); - mPendingGeoUri = null; + clearPending(); setSelectedConversation(conversationList.get(0)); this.mConversationFragment.reInit(getSelectedConversation()); + } else { + this.mConversationFragment.messagesView.invalidateViews(); + this.mConversationFragment.setupIme(); } - for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { - attachImageToConversation(getSelectedConversation(),i.next()); + if (this.mPostponedActivityResult != null) { + this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); } - for(Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { - attachFileToConversation(getSelectedConversation(),i.next()); + if (!forbidProcessingPendings) { + for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { + Uri foo = i.next(); + attachImageToConversation(getSelectedConversation(), foo); + } + + for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { + attachFileToConversation(getSelectedConversation(), i.next()); + } + + if (mPendingGeoUri != null) { + attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); + mPendingGeoUri = null; + } } + forbidProcessingPendings = false; - if (mPendingGeoUri != null) { - attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); - mPendingGeoUri = null; + if (!ExceptionHelper.checkForCrash(this, this.xmppConnectionService)) { + openBatteryOptimizationDialogIfNeeded(); } - ExceptionHelper.checkForCrash(this, this.xmppConnectionService); setIntent(new Intent()); } @@ -973,10 +1198,21 @@ public class ConversationActivity extends XmppActivity final String downloadUuid = intent.getStringExtra(MESSAGE); final String text = intent.getStringExtra(TEXT); final String nick = intent.getStringExtra(NICK); + final boolean pm = intent.getBooleanExtra(PRIVATE_MESSAGE, false); if (selectConversationByUuid(uuid)) { this.mConversationFragment.reInit(getSelectedConversation()); if (nick != null) { - this.mConversationFragment.highlightInConference(nick); + if (pm) { + Jid jid = getSelectedConversation().getJid(); + try { + Jid next = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), nick); + this.mConversationFragment.privateMessageWith(next); + } catch (final InvalidJidException ignored) { + //do nothing + } + } else { + this.mConversationFragment.highlightInConference(nick); + } } else { this.mConversationFragment.appendText(text); } @@ -988,7 +1224,7 @@ public class ConversationActivity extends XmppActivity if (downloadUuid != null) { final Message message = mSelectedConversation.findMessageWithFileAndUuid(downloadUuid); if (message != null) { - mConversationFragment.messageListAdapter.startDownloadable(message); + startDownloadable(message); } } } @@ -1016,10 +1252,13 @@ public class ConversationActivity extends XmppActivity @SuppressLint("NewApi") private static List<Uri> extractUriFromIntent(final Intent intent) { List<Uri> uris = new ArrayList<>(); + if (intent == null) { + return uris; + } Uri uri = intent.getData(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && uri == null) { ClipData clipData = intent.getClipData(); - for(int i = 0; i < clipData.getItemCount(); ++i) { + for (int i = 0; i < clipData.getItemCount(); ++i) { uris.add(clipData.getItemAt(i).getUri()); } } else { @@ -1029,26 +1268,46 @@ public class ConversationActivity extends XmppActivity } @Override - protected void onActivityResult(int requestCode, int resultCode, - final Intent data) { + protected void onActivityResult(int requestCode, int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { if (requestCode == REQUEST_DECRYPT_PGP) { - mConversationFragment.hideSnackbar(); - mConversationFragment.updateMessages(); + mConversationFragment.onActivityResult(requestCode, resultCode, data); + } else if (requestCode == REQUEST_CHOOSE_PGP_ID) { + // the user chose OpenPGP for encryption and selected his key in the PGP provider + if (xmppConnectionServiceBound) { + if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) { + // associate selected PGP keyId with the account + mSelectedConversation.getAccount().setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID)); + // we need to announce the key as described in XEP-027 + announcePgp(mSelectedConversation.getAccount(), null); + } else { + choosePgpSignId(mSelectedConversation.getAccount()); + } + this.mPostponedActivityResult = null; + } else { + this.mPostponedActivityResult = new Pair<>(requestCode, data); + } + } else if (requestCode == REQUEST_ANNOUNCE_PGP) { + if (xmppConnectionServiceBound) { + announcePgp(mSelectedConversation.getAccount(), mSelectedConversation); + this.mPostponedActivityResult = null; + } else { + this.mPostponedActivityResult = new Pair<>(requestCode, data); + } } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) { mPendingImageUris.clear(); mPendingImageUris.addAll(extractUriFromIntent(data)); if (xmppConnectionServiceBound) { - for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { - attachImageToConversation(getSelectedConversation(),i.next()); + for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) { + attachImageToConversation(getSelectedConversation(), i.next()); } } } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE) { mPendingFileUris.clear(); mPendingFileUris.addAll(extractUriFromIntent(data)); if (xmppConnectionServiceBound) { - for(Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { + for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) { attachFileToConversation(getSelectedConversation(), i.next()); } } @@ -1066,66 +1325,109 @@ public class ConversationActivity extends XmppActivity mPendingImageUris.clear(); } } else if (requestCode == ATTACHMENT_CHOICE_LOCATION) { - double latitude = data.getDoubleExtra("latitude",0); - double longitude = data.getDoubleExtra("longitude",0); - this.mPendingGeoUri = Uri.parse("geo:"+String.valueOf(latitude)+","+String.valueOf(longitude)); + double latitude = data.getDoubleExtra("latitude", 0); + double longitude = data.getDoubleExtra("longitude", 0); + this.mPendingGeoUri = Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude)); if (xmppConnectionServiceBound) { attachLocationToConversation(getSelectedConversation(), mPendingGeoUri); this.mPendingGeoUri = null; } + } else if (requestCode == REQUEST_TRUST_KEYS_TEXT || requestCode == REQUEST_TRUST_KEYS_MENU) { + this.forbidProcessingPendings = !xmppConnectionServiceBound; + if (xmppConnectionServiceBound) { + mConversationFragment.onActivityResult(requestCode, resultCode, data); + this.mPostponedActivityResult = null; + } else { + this.mPostponedActivityResult = new Pair<>(requestCode, data); + } + } } else { - mPendingImageUris.clear(); - mPendingFileUris.clear(); + mPendingImageUris.clear(); + mPendingFileUris.clear(); + if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { + mConversationFragment.onActivityResult(requestCode, resultCode, data); + } + if (requestCode == REQUEST_BATTERY_OP) { + setNeverAskForBatteryOptimizationsAgain(); + } } } + private void setNeverAskForBatteryOptimizationsAgain() { + getPreferences().edit().putBoolean("show_battery_optimization", false).commit(); + } + + private void openBatteryOptimizationDialogIfNeeded() { + if (showBatteryOptimizationWarning() && getPreferences().getBoolean("show_battery_optimization", true)) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.battery_optimizations_enabled); + builder.setMessage(R.string.battery_optimizations_enabled_dialog); + builder.setPositiveButton(R.string.next, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + Uri uri = Uri.parse("package:" + getPackageName()); + intent.setData(uri); + startActivityForResult(intent, REQUEST_BATTERY_OP); + } + }); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + setNeverAskForBatteryOptimizationsAgain(); + } + }); + } + builder.create().show(); + } + } + private void attachLocationToConversation(Conversation conversation, Uri uri) { - if (conversation == null) { - return; - } xmppConnectionService.attachLocationToConversation(conversation, uri, new UiCallback<Message>() { - @Override - public void success(Message message) { - xmppConnectionService.sendMessage(message); - } + @Override + public void success(Message message) { + xmppConnectionService.sendMessage(message); + } - @Override - public void error(int errorCode, Message object) { + @Override + public void error(int errorCode, Message object) { - } + } - @Override - public void userInputRequried(PendingIntent pi, Message object) { + @Override + public void userInputRequried(PendingIntent pi, Message object) { - } - }); + } + }); } private void attachFileToConversation(Conversation conversation, Uri uri) { if (conversation == null) { return; } - prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_file), Toast.LENGTH_LONG); + final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_file), Toast.LENGTH_LONG); prepareFileToast.show(); xmppConnectionService.attachFileToConversation(conversation, uri, new UiCallback<Message>() { - @Override - public void success(Message message) { - hidePrepareFileToast(); - xmppConnectionService.sendMessage(message); - } + @Override + public void success(Message message) { + hidePrepareFileToast(prepareFileToast); + xmppConnectionService.sendMessage(message); + } - @Override - public void error(int errorCode, Message message) { - displayErrorDialog(errorCode); - } + @Override + public void error(int errorCode, Message message) { + hidePrepareFileToast(prepareFileToast); + displayErrorDialog(errorCode); + } - @Override - public void userInputRequried(PendingIntent pi, Message message) { + @Override + public void userInputRequried(PendingIntent pi, Message message) { - } - }); + } + }); } private void attachImageToConversation(Conversation conversation, Uri uri) { @@ -1137,7 +1439,7 @@ public class ConversationActivity extends XmppActivity userDecisionDialog.decide(ConversationsPlusPreferences.resizePicture()); } - private void hidePrepareFileToast() { + private void hidePrepareFileToast(final Toast prepareFileToast) { if (prepareFileToast != null) { runOnUiThread(new Runnable() { @@ -1176,7 +1478,7 @@ public class ConversationActivity extends XmppActivity @Override public void userInputRequried(PendingIntent pi, - Message message) { + Message message) { ConversationActivity.this.runIntent(pi, ConversationActivity.REQUEST_SEND_MESSAGE); } @@ -1194,26 +1496,38 @@ public class ConversationActivity extends XmppActivity }); } + protected boolean trustKeysIfNeeded(int requestCode) { + return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID); + } + + protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) { + AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService(); + Contact contact = mSelectedConversation.getContact(); + boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED).isEmpty(); + boolean hasUndecidedContact = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED,contact).isEmpty(); + boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty(); + boolean hasNoTrustedKeys = axolotlService.getNumTrustedKeys(mSelectedConversation.getContact()) == 0; + if(hasUndecidedOwn || hasUndecidedContact || hasPendingKeys || hasNoTrustedKeys) { + axolotlService.createSessionsIfNeeded(mSelectedConversation); + Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class); + intent.putExtra("contact", mSelectedConversation.getContact().getJid().toBareJid().toString()); + intent.putExtra(EXTRA_ACCOUNT, mSelectedConversation.getAccount().getJid().toBareJid().toString()); + intent.putExtra("choice", attachmentChoice); + intent.putExtra("has_no_trusted", hasNoTrustedKeys); + startActivityForResult(intent, requestCode); + return true; + } else { + return false; + } + } + @Override protected void refreshUiReal() { updateConversationList(); - if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 0) { - if (!mRedirected) { - this.mRedirected = true; - startActivity(new Intent(this, EditAccountActivity.class)); - finish(); - } - } else if (conversationList.size() == 0) { - if (!mRedirected) { - this.mRedirected = true; - Intent intent = new Intent(this, StartConversationActivity.class); - intent.putExtra("init",true); - startActivity(intent); - finish(); - } - } else { + if (conversationList.size() > 0) { ConversationActivity.this.mConversationFragment.updateMessages(); updateActionBarTitle(); + invalidateOptionsMenu(); } } @@ -1235,12 +1549,6 @@ public class ConversationActivity extends XmppActivity @Override public void OnUpdateBlocklist(Status status) { this.refreshUi(); - runOnUiThread(new Runnable() { - @Override - public void run() { - invalidateOptionsMenu(); - } - }); } public void unblockConversation(final Blockable conversation) { diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java index 3ee79412..2828e27e 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java @@ -1,5 +1,6 @@ package de.thedevstack.conversationsplus.ui; +import android.app.Activity; import android.app.AlertDialog; import android.app.Fragment; import android.app.PendingIntent; @@ -7,9 +8,9 @@ import android.content.ActivityNotFoundException; 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.support.annotation.Nullable; import android.text.InputType; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -38,32 +39,31 @@ import com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout; import net.java.otr4j.session.SessionStatus; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.NoSuchElementException; -import java.util.concurrent.ConcurrentLinkedQueue; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; -import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.ui.dialogs.MessageDetailsDialog; -import de.thedevstack.conversationsplus.ui.listeners.ConversationSwipeRefreshListener; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; -import de.thedevstack.conversationsplus.crypto.PgpEngine; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.Conversation; -import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.entities.DownloadableFile; -import de.thedevstack.conversationsplus.entities.TransferablePlaceholder; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.MucOptions; -import de.thedevstack.conversationsplus.entities.Presences; +import de.thedevstack.conversationsplus.entities.Presence; +import de.thedevstack.conversationsplus.entities.Transferable; +import de.thedevstack.conversationsplus.entities.TransferablePlaceholder; +import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.ui.XmppActivity.OnPresenceSelected; import de.thedevstack.conversationsplus.ui.XmppActivity.OnValueEdited; import de.thedevstack.conversationsplus.ui.adapter.MessageAdapter; import de.thedevstack.conversationsplus.ui.adapter.MessageAdapter.OnContactPictureClicked; import de.thedevstack.conversationsplus.ui.adapter.MessageAdapter.OnContactPictureLongClicked; +import de.thedevstack.conversationsplus.ui.listeners.ConversationSwipeRefreshListener; import de.thedevstack.conversationsplus.utils.GeoHelper; import de.thedevstack.conversationsplus.utils.UIHelper; import de.thedevstack.conversationsplus.xmpp.chatstate.ChatState; @@ -110,7 +110,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa }; protected ListView messagesView; protected SwipyRefreshLayout swipeLayout; - final protected List<Message> messageList = new ArrayList<>(); + final protected List<Message> messageList = new ArrayList<>(); protected MessageAdapter messageListAdapter; private EditMessage mEditMessage; private ImageButton mSendButton; @@ -120,21 +120,49 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private RelativeLayout snackbar; private TextView snackbarMessage; private TextView snackbarAction; - private IntentSender askForPassphraseIntent = null; + private boolean messagesLoaded = true; + private Toast messageLoaderToast; + private final int KEYCHAIN_UNLOCK_NOT_REQUIRED = 0; + private final int KEYCHAIN_UNLOCK_REQUIRED = 1; + private final int KEYCHAIN_UNLOCK_PENDING = 2; + private int keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; 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); - askForPassphraseIntent = null; - } catch (SendIntentException e) { - // + if (keychainUnlock == KEYCHAIN_UNLOCK_REQUIRED + && activity.hasPgp() && !conversation.getAccount().getPgpDecryptionService().isRunning()) { + keychainUnlock = KEYCHAIN_UNLOCK_PENDING; + updateSnackBar(conversation); + Message message = getLastPgpDecryptableMessage(); + if (message != null) { + activity.xmppConnectionService.getPgpEngine().decrypt(message, new UiCallback<Message>() { + @Override + public void success(Message object) { + conversation.getAccount().getPgpDecryptionService().onKeychainUnlocked(); + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + } + + @Override + public void error(int errorCode, Message object) { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + } + + @Override + public void userInputRequried(PendingIntent pi, Message object) { + try { + activity.startIntentSenderForResult(pi.getIntentSender(), + ConversationActivity.REQUEST_DECRYPT_PGP, null, 0, 0, 0); + } catch (SendIntentException e) { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + updatePgpMessages(); + } + } + }); } + } else { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + updatePgpMessages(); } } }; @@ -145,8 +173,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa activity.verifyOtrSessionDialog(conversation, v); } }; - private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<>(); - private boolean mDecryptJobRunning = false; private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() { @Override @@ -154,7 +180,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (actionId == EditorInfo.IME_ACTION_SEND) { InputMethodManager imm = (InputMethodManager) v.getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + if (imm.isFullscreenMode()) { + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + } sendMessage(); return true; } else { @@ -210,43 +238,64 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private ConversationActivity activity; private Message selectedMessage; + public void setMessagesLoaded() { + this.messagesLoaded = true; + } + private void sendMessage() { final String body = mEditMessage.getText().toString(); if (body.length() == 0 || this.conversation == null) { return; } - boolean forceEncryption = ConversationsPlusPreferences.forceEncryption(); - Message message = new Message(conversation, body, conversation.getNextEncryption(forceEncryption)); + Message message = new Message(conversation, body, conversation.getNextEncryption()); if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getNextCounterpart() != null) { message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_PRIVATE); } } - if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_OTR) { - sendOtrMessage(message); - } else if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) { - sendPgpMessage(message); - } else { - sendPlainTextMessage(message); + switch (conversation.getNextEncryption()) { + case Message.ENCRYPTION_OTR: + sendOtrMessage(message); + break; + case Message.ENCRYPTION_PGP: + sendPgpMessage(message); + break; + case Message.ENCRYPTION_AXOLOTL: + if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) { + sendAxolotlMessage(message); + } + break; + default: + sendPlainTextMessage(message); } } public void updateChatMsgHint() { - if (conversation.getMode() == Conversation.MODE_MULTI - && conversation.getNextCounterpart() != null) { + final boolean multi = conversation.getMode() == Conversation.MODE_MULTI; + if (multi && conversation.getNextCounterpart() != null) { this.mEditMessage.setHint(getString( - R.string.send_private_message_to, - conversation.getNextCounterpart().getResourcepart())); + R.string.send_private_message_to, + conversation.getNextCounterpart().getResourcepart())); + } else if (multi && !conversation.getMucOptions().participating()) { + this.mEditMessage.setHint(R.string.you_are_not_participating); } else { - switch (conversation.getNextEncryption(ConversationsPlusPreferences.forceEncryption())) { + switch (conversation.getNextEncryption()) { case Message.ENCRYPTION_NONE: mEditMessage - .setHint(getString(R.string.send_plain_text_message)); + .setHint(getString(R.string.send_unencrypted_message)); break; case Message.ENCRYPTION_OTR: mEditMessage.setHint(getString(R.string.send_otr_message)); break; + case Message.ENCRYPTION_AXOLOTL: + AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); + if (axolotlService != null && axolotlService.trustedSessionVerified(conversation)) { + mEditMessage.setHint(getString(R.string.send_omemo_x509_message)); + } else { + mEditMessage.setHint(getString(R.string.send_omemo_message)); + } + break; case Message.ENCRYPTION_PGP: mEditMessage.setHint(getString(R.string.send_pgp_message)); break; @@ -257,21 +306,26 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } - private void setupIme() { - if (ConversationsPlusPreferences.displayEnterKey()) { + public void setupIme() { + if (activity == null) { + return; + } else if (activity.usingEnterKey() && ConversationsPlusPreferences.enterIsSend()) { + mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE)); + mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); + } else if (activity.usingEnterKey()) { + mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE); mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); } else { + mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE); mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE); } } @Override - public View onCreateView(final LayoutInflater inflater, - ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.fragment_conversation, container, false); view.setOnClickListener(null); mEditMessage = (EditMessage) view.findViewById(R.id.textinput); - setupIme(); mEditMessage.setOnClickListener(new OnClickListener() { @Override @@ -401,19 +455,20 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (message.getStatus() <= Message.STATUS_RECEIVED) { if (message.getConversation().getMode() == Conversation.MODE_MULTI) { if (message.getCounterpart() != null) { - if (!message.getCounterpart().isBareJid()) { - highlightInConference(message.getCounterpart().getResourcepart()); - } else { - highlightInConference(message.getCounterpart().toString()); + String user = message.getCounterpart().isBareJid() ? message.getCounterpart().toString() : message.getCounterpart().getResourcepart(); + if (!message.getConversation().getMucOptions().isUserInRoom(user)) { + Toast.makeText(activity,activity.getString(R.string.user_has_left_conference,user),Toast.LENGTH_SHORT).show(); } + highlightInConference(user); } } else { - activity.switchToContactDetails(message.getContact()); + activity.switchToContactDetails(message.getContact(), message.getAxolotlFingerprint()); } } else { Account account = message.getConversation().getAccount(); Intent intent = new Intent(activity, EditAccountActivity.class); intent.putExtra("jid", account.getJid().toBareJid().toString()); + intent.putExtra("fingerprint", message.getAxolotlFingerprint()); startActivity(intent); } } @@ -426,7 +481,14 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (message.getStatus() <= Message.STATUS_RECEIVED) { if (message.getConversation().getMode() == Conversation.MODE_MULTI) { if (message.getCounterpart() != null) { - privateMessageWith(message.getCounterpart()); + String user = message.getCounterpart().getResourcepart(); + if (user != null) { + if (message.getConversation().getMucOptions().isUserInRoom(user)) { + privateMessageWith(message.getCounterpart()); + } else { + Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user), Toast.LENGTH_SHORT).show(); + } + } } } } else { @@ -448,8 +510,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { synchronized (this.messageList) { super.onCreateContextMenu(menu, v, menuInfo); AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; @@ -464,6 +525,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa activity.getMenuInflater().inflate(R.menu.message_context, menu); menu.setHeaderTitle(R.string.message_options); MenuItem copyText = menu.findItem(R.id.copy_text); + MenuItem retryDecryption = menu.findItem(R.id.retry_decryption); MenuItem shareWith = menu.findItem(R.id.share_with); MenuItem sendAgain = menu.findItem(R.id.send_again); MenuItem copyUrl = menu.findItem(R.id.copy_url); @@ -475,6 +537,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa && m.treatAsDownloadable() != Message.Decision.MUST) { copyText.setVisible(true); } + + if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { + retryDecryption.setVisible(true); + } + if ((m.getType() != Message.TYPE_TEXT && m.getType() != Message.TYPE_PRIVATE && m.getTransferable() == null) @@ -489,7 +556,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa || m.treatAsDownloadable() == Message.Decision.MUST) { copyUrl.setVisible(true); } - if (m.getType() == Message.TYPE_TEXT && m.getTransferable() == null && m.treatAsDownloadable() != Message.Decision.NEVER) { + if ((m.getType() == Message.TYPE_TEXT && m.getTransferable() == null && m.treatAsDownloadable() != Message.Decision.NEVER) + || (m.isFileOrImage() && m.getTransferable() instanceof TransferablePlaceholder && m.hasFileOnRemoteHost())){ downloadFile.setVisible(true); downloadFile.setTitle(activity.getString(R.string.download_x_file,UIHelper.getFileDescriptionString(activity, m))); } @@ -504,9 +572,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.msg_ctx_mnu_details: + case R.id.msg_ctx_mnu_details: new MessageDetailsDialog(getActivity(), selectedMessage).show(); - return true; + return true; case R.id.share_with: shareWith(selectedMessage); return true; @@ -525,6 +593,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa case R.id.cancel_transmission: cancelTransmission(selectedMessage); return true; + case R.id.retry_decryption: + retryDecryption(selectedMessage); + return true; default: return super.onContextItemSelected(item); } @@ -537,11 +608,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody()); shareIntent.setType("text/plain"); } else { - shareIntent.putExtra(Intent.EXTRA_STREAM, FileBackend.getJingleFileUri(message)); + shareIntent.putExtra(Intent.EXTRA_STREAM, + FileBackend.getJingleFileUri(message)); shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); String mime = message.getMimeType(); if (mime == null) { - mime = "image/webp"; + mime = "*/*"; } shareIntent.setType(mime); } @@ -594,7 +666,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private void downloadFile(Message message) { activity.xmppConnectionService.getHttpConnectionManager() - .createNewDownloadConnection(message); + .createNewDownloadConnection(message,true); } private void cancelTransmission(Message message) { @@ -606,6 +678,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } + private void retryDecryption(Message message) { + message.setEncryption(Message.ENCRYPTION_PGP); + activity.xmppConnectionService.updateConversationUi(); + conversation.getAccount().getPgpDecryptionService().add(message); + } + protected void privateMessageWith(final Jid counterpart) { this.mEditMessage.setText(""); this.conversation.setNextCounterpart(counterpart); @@ -629,7 +707,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void onStop() { - mDecryptJobRunning = false; super.onStop(); if (this.conversation != null) { final String msg = mEditMessage.getText().toString(); @@ -650,9 +727,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (conversation == null) { return; } - this.activity = (ConversationActivity) getActivity(); - + setupIme(); if (this.conversation != null) { final String msg = mEditMessage.getText().toString(); this.conversation.setNextMessage(msg); @@ -662,19 +738,18 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa this.conversation.trim(); } - this.askForPassphraseIntent = null; + this.keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; this.conversation = conversation; - this.mDecryptJobRunning = false; - this.mEncryptedMessages.clear(); - if (this.conversation.getMode() == Conversation.MODE_MULTI) { - this.conversation.setNextCounterpart(null); - } + boolean canWrite = this.conversation.getMode() == Conversation.MODE_SINGLE || this.conversation.getMucOptions().participating(); + this.mEditMessage.setEnabled(canWrite); + this.mSendButton.setEnabled(canWrite); this.mEditMessage.setKeyboardListener(null); this.mEditMessage.setText(""); this.mEditMessage.append(this.conversation.getNextMessage()); this.mEditMessage.setKeyboardListener(this); this.messagesView.setAdapter(messageListAdapter); updateMessages(); + this.messagesLoaded = true; int size = this.messageList.size(); if (size > 0) { messagesView.setSelection(size - 1); @@ -711,21 +786,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } }; - private OnClickListener mUnmuteClickListener = new OnClickListener() { - - @Override - public void onClick(final View v) { - activity.unmuteConversation(conversation); - } - }; - private OnClickListener mAnswerSmpClickListener = new OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(activity, VerifyOTRActivity.class); intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); - intent.putExtra("account", conversation.getAccount().getJid().toBareJid().toString()); + intent.putExtra(VerifyOTRActivity.EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); intent.putExtra("mode", VerifyOTRActivity.MODE_ANSWER_QUESTION); startActivity(intent); } @@ -743,28 +810,34 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa && !conversation.getMucOptions().online() && account.getStatus() == Account.State.ONLINE) { switch (conversation.getMucOptions().getError()) { - case MucOptions.ERROR_NICK_IN_USE: + case NICK_IN_USE: showSnackbar(R.string.nick_in_use, R.string.edit, clickToMuc); break; - case MucOptions.ERROR_UNKNOWN: - showSnackbar(R.string.conference_not_found, R.string.leave, leaveMuc); + case NO_RESPONSE: + showSnackbar(R.string.joining_conference, 0, null); break; - case MucOptions.ERROR_PASSWORD_REQUIRED: + case PASSWORD_REQUIRED: showSnackbar(R.string.conference_requires_password, R.string.enter_password, enterPassword); break; - case MucOptions.ERROR_BANNED: + case BANNED: showSnackbar(R.string.conference_banned, R.string.leave, leaveMuc); break; - case MucOptions.ERROR_MEMBERS_ONLY: + case MEMBERS_ONLY: showSnackbar(R.string.conference_members_only, R.string.leave, leaveMuc); break; - case MucOptions.KICKED_FROM_ROOM: + case KICKED: showSnackbar(R.string.conference_kicked, R.string.join, joinMuc); break; + case UNKNOWN: + showSnackbar(R.string.conference_unknown_error, R.string.join, joinMuc); + break; + case SHUTDOWN: + showSnackbar(R.string.conference_shutdown, R.string.join, joinMuc); + break; default: break; } - } else if (askForPassphraseIntent != null) { + } else if (keychainUnlock == KEYCHAIN_UNLOCK_REQUIRED) { showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener); } else if (mode == Conversation.MODE_SINGLE && conversation.smpRequested()) { @@ -774,8 +847,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!conversation.isOtrFingerprintVerified())) { showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify); - } else if (conversation.isMuted()) { - showSnackbar(R.string.notifications_disabled, R.string.enable, this.mUnmuteClickListener); } else { hideSnackbar(); } @@ -788,19 +859,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } final ConversationActivity activity = (ConversationActivity) getActivity(); if (this.conversation != null) { - updateSnackBar(this.conversation); conversation.populateWithMessages(ConversationFragment.this.messageList); - for (final Message message : this.messageList) { - if (message.getEncryption() == Message.ENCRYPTION_PGP - && (message.getStatus() == Message.STATUS_RECEIVED || message - .getStatus() >= Message.STATUS_SEND) - && message.getTransferable() == null) { - if (!mEncryptedMessages.contains(message)) { - mEncryptedMessages.add(message); - } - } - } - decryptNext(); + updatePgpMessages(); + updateSnackBar(conversation); updateStatusMessages(); this.messageListAdapter.notifyDataSetChanged(); updateChatMsgHint(); @@ -812,46 +873,27 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } - 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(); - updateSnackBar(conversation); - } - - @Override - public void success(Message message) { - mDecryptJobRunning = false; - try { - mEncryptedMessages.remove(); - } catch (final NoSuchElementException ignored) { - - } - askForPassphraseIntent = null; - activity.xmppConnectionService.updateMessage(message); - } - - @Override - public void error(int error, Message message) { - message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED); - mDecryptJobRunning = false; - try { - mEncryptedMessages.remove(); - } catch (final NoSuchElementException ignored) { + public void updatePgpMessages() { + if (keychainUnlock != KEYCHAIN_UNLOCK_PENDING) { + if (getLastPgpDecryptableMessage() != null + && !conversation.getAccount().getPgpDecryptionService().isRunning()) { + keychainUnlock = KEYCHAIN_UNLOCK_REQUIRED; + } else { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + } + } + } - } - activity.xmppConnectionService.updateConversationUi(); - } - }); + @Nullable + private Message getLastPgpDecryptableMessage() { + for (final Message message : this.messageList) { + if (message.getEncryption() == Message.ENCRYPTION_PGP + && (message.getStatus() == Message.STATUS_RECEIVED || message.getStatus() >= Message.STATUS_SEND) + && message.getTransferable() == null) { + return message; + } } + return null; } private void messageSent() { @@ -861,84 +903,88 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa updateChatMsgHint(); } + public void setFocusOnInputField() { + mEditMessage.requestFocus(); + } + enum SendButtonAction {TEXT, TAKE_PHOTO, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE} - private int getSendButtonImageResource(SendButtonAction action, int status) { + private int getSendButtonImageResource(SendButtonAction action, Presence.Status status) { switch (action) { case TEXT: switch (status) { - case Presences.CHAT: - case Presences.ONLINE: + case CHAT: + case ONLINE: return R.drawable.ic_send_text_online; - case Presences.AWAY: + case AWAY: return R.drawable.ic_send_text_away; - case Presences.XA: - case Presences.DND: + case XA: + case DND: return R.drawable.ic_send_text_dnd; default: return R.drawable.ic_send_text_offline; } case TAKE_PHOTO: switch (status) { - case Presences.CHAT: - case Presences.ONLINE: + case CHAT: + case ONLINE: return R.drawable.ic_send_photo_online; - case Presences.AWAY: + case AWAY: return R.drawable.ic_send_photo_away; - case Presences.XA: - case Presences.DND: + case XA: + case DND: return R.drawable.ic_send_photo_dnd; default: return R.drawable.ic_send_photo_offline; } case RECORD_VOICE: switch (status) { - case Presences.CHAT: - case Presences.ONLINE: + case CHAT: + case ONLINE: return R.drawable.ic_send_voice_online; - case Presences.AWAY: + case AWAY: return R.drawable.ic_send_voice_away; - case Presences.XA: - case Presences.DND: + case XA: + case DND: return R.drawable.ic_send_voice_dnd; default: return R.drawable.ic_send_voice_offline; } case SEND_LOCATION: switch (status) { - case Presences.CHAT: - case Presences.ONLINE: + case CHAT: + case ONLINE: return R.drawable.ic_send_location_online; - case Presences.AWAY: + case AWAY: return R.drawable.ic_send_location_away; - case Presences.XA: - case Presences.DND: + case XA: + case DND: return R.drawable.ic_send_location_dnd; default: return R.drawable.ic_send_location_offline; } case CANCEL: switch (status) { - case Presences.CHAT: - case Presences.ONLINE: + case CHAT: + case ONLINE: return R.drawable.ic_send_cancel_online; - case Presences.AWAY: + case AWAY: return R.drawable.ic_send_cancel_away; - case Presences.XA: - case Presences.DND: + case XA: + case DND: return R.drawable.ic_send_cancel_dnd; default: return R.drawable.ic_send_cancel_offline; } case CHOOSE_PICTURE: switch (status) { - case Presences.CHAT: - case Presences.ONLINE: + case CHAT: + case ONLINE: return R.drawable.ic_send_picture_online; - case Presences.AWAY: + case AWAY: return R.drawable.ic_send_picture_away; - case Presences.XA: - case Presences.DND: + case XA: + case DND: return R.drawable.ic_send_picture_dnd; default: return R.drawable.ic_send_picture_offline; @@ -950,8 +996,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa public void updateSendButton() { final Conversation c = this.conversation; final SendButtonAction action; - final int status; - final boolean empty = this.mEditMessage == null || this.mEditMessage.getText().length() == 0; + final Presence.Status status; + final String text = this.mEditMessage == null ? "" : this.mEditMessage.getText().toString(); + final boolean empty = text.length() == 0; final boolean conference = c.getMode() == Conversation.MODE_MULTI; if (conference && !c.getAccount().httpUploadAvailable()) { if (empty && c.getNextCounterpart() != null) { @@ -997,10 +1044,10 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (c.getMode() == Conversation.MODE_SINGLE) { status = c.getContact().getMostAvailableStatus(); } else { - status = c.getMucOptions().online() ? Presences.ONLINE : Presences.OFFLINE; + status = c.getMucOptions().online() ? Presence.Status.ONLINE : Presence.Status.OFFLINE; } } else { - status = Presences.OFFLINE; + status = Presence.Status.OFFLINE; } this.mSendButton.setTag(action); this.mSendButton.setImageResource(getSendButtonImageResource(action, status)); @@ -1031,14 +1078,15 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } - protected void showSnackbar(final int message, final int action, - final OnClickListener clickListener) { + protected void showSnackbar(final int message, final int action, final OnClickListener clickListener) { snackbar.setVisibility(View.VISIBLE); snackbar.setOnClickListener(null); snackbarMessage.setText(message); snackbarMessage.setOnClickListener(null); - snackbarAction.setVisibility(View.VISIBLE); - snackbarAction.setText(action); + snackbarAction.setVisibility(clickListener == null ? View.GONE : View.VISIBLE); + if (action != 0) { + snackbarAction.setText(action); + } snackbarAction.setOnClickListener(clickListener); } @@ -1056,81 +1104,85 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa 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); - } + if (!activity.hasPgp()) { + activity.showInstallPgpDialog(); + return; + } + if (conversation.getAccount().getPgpSignature() == null) { + activity.announcePgp(conversation.getAccount(), conversation); + return; + } + 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 error(int error, Contact contact) { + @Override + public void success(Contact contact) { + messageSent(); + activity.encryptTextMessage(message); + } - } - }); + @Override + public void error(int error, Contact contact) { + System.out.println(); + } + }); - } 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(); - } - }); - } + 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 { - activity.showInstallPgpDialog(); + 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(); + } + }); + } } } @@ -1151,19 +1203,26 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa builder.create().show(); } + protected void sendAxolotlMessage(final Message message) { + final ConversationActivity activity = (ConversationActivity) getActivity(); + final XmppConnectionService xmppService = activity.xmppConnectionService; + xmppService.sendMessage(message); + messageSent(); + } + protected void sendOtrMessage(final Message message) { final ConversationActivity activity = (ConversationActivity) getActivity(); final XmppConnectionService xmppService = activity.xmppConnectionService; activity.selectPresence(message.getConversation(), - new OnPresenceSelected() { + new OnPresenceSelected() { - @Override - public void onPresenceSelected() { - message.setCounterpart(conversation.getNextCounterpart()); - xmppService.sendMessage(message); - messageSent(); - } - }); + @Override + public void onPresenceSelected() { + message.setCounterpart(conversation.getNextCounterpart()); + xmppService.sendMessage(message); + messageSent(); + } + }); } public void appendText(String text) { @@ -1193,6 +1252,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) { activity.xmppConnectionService.sendChatState(conversation); } + activity.hideConversationsOverview(); updateSendButton(); } @@ -1213,6 +1273,72 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa updateSendButton(); } + private int completionIndex = 0; + private int lastCompletionLength = 0; + private String incomplete; + private int lastCompletionCursor; + private boolean firstWord = false; + + @Override + public boolean onTabPressed(boolean repeated) { + if (conversation == null || conversation.getMode() == Conversation.MODE_SINGLE) { + return false; + } + if (repeated) { + completionIndex++; + } else { + lastCompletionLength = 0; + completionIndex = 0; + final String content = mEditMessage.getText().toString(); + lastCompletionCursor = mEditMessage.getSelectionEnd(); + int start = lastCompletionCursor > 0 ? content.lastIndexOf(" ",lastCompletionCursor-1) + 1 : 0; + firstWord = start == 0; + incomplete = content.substring(start,lastCompletionCursor); + } + List<String> completions = new ArrayList<>(); + for(MucOptions.User user : conversation.getMucOptions().getUsers()) { + if (user.getName().startsWith(incomplete)) { + completions.add(user.getName()+(firstWord ? ": " : " ")); + } + } + Collections.sort(completions); + if (completions.size() > completionIndex) { + String completion = completions.get(completionIndex).substring(incomplete.length()); + mEditMessage.getEditableText().delete(lastCompletionCursor,lastCompletionCursor + lastCompletionLength); + mEditMessage.getEditableText().insert(lastCompletionCursor, completion); + lastCompletionLength = completion.length(); + } else { + completionIndex = -1; + mEditMessage.getEditableText().delete(lastCompletionCursor,lastCompletionCursor + lastCompletionLength); + lastCompletionLength = 0; + } + return true; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, + final Intent data) { + if (resultCode == Activity.RESULT_OK) { + if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { + activity.getSelectedConversation().getAccount().getPgpDecryptionService().onKeychainUnlocked(); + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + updatePgpMessages(); + } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) { + final String body = mEditMessage.getText().toString(); + Message message = new Message(conversation, body, conversation.getNextEncryption()); + sendAxolotlMessage(message); + } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) { + int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID); + activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption()); + } + } else { + if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { + keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; + updatePgpMessages(); + } + } + } + private void changeEmojiKeyboardIcon(ImageView iconToBeChanged, int drawableResourceId){ iconToBeChanged.setImageResource(drawableResourceId); } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/EditAccountActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/EditAccountActivity.java index 601ddedc..7da2e889 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/EditAccountActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/EditAccountActivity.java @@ -1,8 +1,17 @@ package de.thedevstack.conversationsplus.ui; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; import android.app.PendingIntent; +import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.net.Uri; import android.os.Bundle; +import android.provider.Settings; +import android.security.KeyChain; +import android.security.KeyChainAliasCallback; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; @@ -20,23 +29,38 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TableLayout; +import android.widget.TableRow; import android.widget.TextView; import android.widget.Toast; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.services.AvatarService; +import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.services.XmppConnectionService.OnAccountUpdate; +import de.thedevstack.conversationsplus.services.XmppConnectionService.OnCaptchaRequested; import de.thedevstack.conversationsplus.ui.adapter.KnownHostsAdapter; -import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener; import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.UIHelper; +import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.OnKeyStatusUpdated; +import de.thedevstack.conversationsplus.xmpp.XmppConnection; import de.thedevstack.conversationsplus.xmpp.XmppConnection.Features; +import de.thedevstack.conversationsplus.xmpp.forms.Data; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.xmpp.pep.Avatar; -public class EditAccountActivity extends XmppActivity implements OnAccountUpdate{ +public class EditAccountActivity extends XmppActivity implements OnAccountUpdate, + OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched { private AutoCompleteTextView mAccountJid; private EditText mPassword; @@ -44,9 +68,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private CheckBox mRegisterNew; private Button mCancelButton; private Button mSaveButton; + private Button mDisableBatterOptimizations; private TableLayout mMoreTable; private LinearLayout mStats; + private RelativeLayout mBatteryOptimizations; private TextView mServerInfoSm; private TextView mServerInfoRosterVersion; private TextView mServerInfoCarbons; @@ -54,14 +80,30 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private TextView mServerInfoCSI; private TextView mServerInfoBlocking; private TextView mServerInfoPep; + private TextView mServerInfoHttpUpload; + private TextView mServerInfoPush; private TextView mSessionEst; private TextView mOtrFingerprint; + private TextView mAxolotlFingerprint; + private TextView mAccountJidLabel; private ImageView mAvatar; private RelativeLayout mOtrFingerprintBox; + private RelativeLayout mAxolotlFingerprintBox; private ImageButton mOtrFingerprintToClipboardButton; + private ImageButton mAxolotlFingerprintToClipboardButton; + private ImageButton mRegenerateAxolotlKeyButton; + private LinearLayout keys; + private LinearLayout keysCard; + private LinearLayout mNamePort; + private EditText mHostname; + private EditText mPort; + private AlertDialog mCaptchaDialog = null; private Jid jidToEdit; + private boolean mInitMode = false; + private boolean mShowOptions = false; private Account mAccount; + private String messageFingerprint; private boolean mFetchingAvatar = false; @@ -69,22 +111,67 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate @Override public void onClick(final View v) { + if (mInitMode && mAccount != null) { + mAccount.setOption(Account.OPTION_DISABLED, false); + } if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited()) { mAccount.setOption(Account.OPTION_DISABLED, false); xmppConnectionService.updateAccount(mAccount); return; } - final boolean registerNewAccount = mRegisterNew.isChecked(); + final boolean registerNewAccount = mRegisterNew.isChecked() && !Config.DISALLOW_REGISTRATION_IN_UI; + if (Config.DOMAIN_LOCK != null && mAccountJid.getText().toString().contains("@")) { + mAccountJid.setError(getString(R.string.invalid_username)); + mAccountJid.requestFocus(); + return; + } final Jid jid; try { - jid = Jid.fromString(mAccountJid.getText().toString()); + if (Config.DOMAIN_LOCK != null) { + jid = Jid.fromParts(mAccountJid.getText().toString(), Config.DOMAIN_LOCK, null); + } else { + jid = Jid.fromString(mAccountJid.getText().toString()); + } } catch (final InvalidJidException e) { - mAccountJid.setError(getString(R.string.invalid_jid)); + if (Config.DOMAIN_LOCK != null) { + mAccountJid.setError(getString(R.string.invalid_username)); + } else { + mAccountJid.setError(getString(R.string.invalid_jid)); + } mAccountJid.requestFocus(); return; } + String hostname = null; + int numericPort = 5222; + if (mShowOptions) { + hostname = mHostname.getText().toString(); + final String port = mPort.getText().toString(); + if (hostname.contains(" ")) { + mHostname.setError(getString(R.string.not_valid_hostname)); + mHostname.requestFocus(); + return; + } + try { + numericPort = Integer.parseInt(port); + if (numericPort < 0 || numericPort > 65535) { + mPort.setError(getString(R.string.not_a_valid_port)); + mPort.requestFocus(); + return; + } + + } catch (NumberFormatException e) { + mPort.setError(getString(R.string.not_a_valid_port)); + mPort.requestFocus(); + return; + } + } + if (jid.isDomainJid()) { - mAccountJid.setError(getString(R.string.invalid_jid)); + if (Config.DOMAIN_LOCK != null) { + mAccountJid.setError(getString(R.string.invalid_username)); + } else { + mAccountJid.setError(getString(R.string.invalid_jid)); + } mAccountJid.requestFocus(); return; } @@ -98,34 +185,33 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } } if (mAccount != null) { - try { - mAccount.setUsername(jid.hasLocalpart() ? jid.getLocalpart() : ""); - mAccount.setServer(jid.getDomainpart()); - } catch (final InvalidJidException ignored) { - return; - } + mAccount.setJid(jid); + mAccount.setPort(numericPort); + mAccount.setHostname(hostname); mAccountJid.setError(null); mPasswordConfirm.setError(null); mAccount.setPassword(password); mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); xmppConnectionService.updateAccount(mAccount); } else { - try { - if (xmppConnectionService.findAccountByJid(Jid.fromString(mAccountJid.getText().toString())) != null) { - mAccountJid.setError(getString(R.string.account_already_exists)); - mAccountJid.requestFocus(); - return; - } - } catch (final InvalidJidException e) { + if (xmppConnectionService.findAccountByJid(jid) != null) { + mAccountJid.setError(getString(R.string.account_already_exists)); + mAccountJid.requestFocus(); return; } mAccount = new Account(jid.toBareJid(), password); + mAccount.setPort(numericPort); + mAccount.setHostname(hostname); mAccount.setOption(Account.OPTION_USETLS, true); mAccount.setOption(Account.OPTION_USECOMPRESSION, true); mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); xmppConnectionService.createAccount(mAccount); } - if (jidToEdit != null && !mAccount.isOptionSet(Account.OPTION_DISABLED)) { + mHostname.setError(null); + mPort.setError(null); + if (!mAccount.isOptionSet(Account.OPTION_DISABLED) + && !registerNewAccount + && !mInitMode) { finish(); } else { updateSaveButton(); @@ -141,35 +227,35 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate finish(); } }; + private Toast mFetchingMamPrefsToast; + private TableRow mPushRow; + + public void refreshUiReal() { + invalidateOptionsMenu(); + if (mAccount != null + && mAccount.getStatus() != Account.State.ONLINE + && mFetchingAvatar) { + startActivity(new Intent(getApplicationContext(), + ManageAccountActivity.class)); + finish(); + } else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) { + if (!mFetchingAvatar) { + mFetchingAvatar = true; + AvatarService.getInstance().checkForAvatar(mAccount, mAvatarFetchCallback); + } + } else { + updateSaveButton(); + } + if (mAccount != null) { + updateAccountInformation(false); + } + } + @Override public void onAccountUpdate() { - runOnUiThread(new Runnable() { - - @Override - public void run() { - invalidateOptionsMenu(); - if (mAccount != null - && mAccount.getStatus() != Account.State.ONLINE - && mFetchingAvatar) { - startActivity(new Intent(getApplicationContext(), - ManageAccountActivity.class)); - finish(); - } else if (jidToEdit == null && mAccount != null - && mAccount.getStatus() == Account.State.ONLINE) { - if (!mFetchingAvatar) { - mFetchingAvatar = true; - AvatarService.getInstance().checkForAvatar(mAccount, - mAvatarFetchCallback); - } - } else { - updateSaveButton(); - } - if (mAccount != null) { - updateAccountInformation(false); - } - } - }); + refreshUi(); } + private final UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() { @Override @@ -208,9 +294,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate @Override public void onClick(final View view) { if (mAccount != null) { - final Intent intent = new Intent(getApplicationContext(), - PublishProfilePictureActivity.class); - intent.putExtra("account", mAccount.getJid().toBareJid().toString()); + final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); + intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toBareJid().toString()); startActivity(intent); } } @@ -222,16 +307,15 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate @Override public void run() { final Intent intent; - if (avatar != null) { - intent = new Intent(getApplicationContext(), - StartConversationActivity.class); + final XmppConnection connection = mAccount.getXmppConnection(); + if (avatar != null || (connection != null && !connection.getFeatures().pep())) { + intent = new Intent(getApplicationContext(), StartConversationActivity.class); if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1) { intent.putExtra("init", true); } } else { - intent = new Intent(getApplicationContext(), - PublishProfilePictureActivity.class); - intent.putExtra("account", mAccount.getJid().toBareJid().toString()); + intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); + intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toBareJid().toString()); intent.putExtra("setup", true); } startActivity(intent); @@ -240,8 +324,16 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate }); } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_BATTERY_OP) { + updateAccountInformation(mAccount == null); + } + } + protected void updateSaveButton() { - if (accountInfoEdited() && jidToEdit != null) { + if (accountInfoEdited() && !mInitMode) { this.mSaveButton.setText(R.string.save); this.mSaveButton.setEnabled(true); this.mSaveButton.setTextColor(getPrimaryTextColor()); @@ -249,14 +341,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mSaveButton.setEnabled(false); this.mSaveButton.setTextColor(getSecondaryTextColor()); this.mSaveButton.setText(R.string.account_status_connecting); - } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED) { + } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !mInitMode) { 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 (!mInitMode) { if (mAccount != null && mAccount.isOnlineAndConnected()) { this.mSaveButton.setText(R.string.save); if (!accountInfoEdited()) { @@ -273,15 +365,24 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } protected boolean accountInfoEdited() { - return this.mAccount != null && (!this.mAccount.getJid().toBareJid().toString().equals( - this.mAccountJid.getText().toString()) - || !this.mAccount.getPassword().equals( - this.mPassword.getText().toString())); + if (this.mAccount == null) { + return false; + } + final String unmodified; + if (Config.DOMAIN_LOCK != null) { + unmodified = this.mAccount.getJid().getLocalpart(); + } else { + unmodified = this.mAccount.getJid().toBareJid().toString(); + } + return !unmodified.equals(this.mAccountJid.getText().toString()) || + !this.mAccount.getPassword().equals(this.mPassword.getText().toString()) || + !this.mAccount.getHostname().equals(this.mHostname.getText().toString()) || + !String.valueOf(this.mAccount.getPort()).equals(this.mPort.getText().toString()); } @Override protected String getShareableUri() { - if (mAccount!=null) { + if (mAccount != null) { return mAccount.getShareableUri(); } else { return ""; @@ -294,6 +395,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate setContentView(R.layout.activity_edit_account); this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid); this.mAccountJid.addTextChangedListener(this.mTextWatcher); + this.mAccountJidLabel = (TextView) findViewById(R.id.account_jid_label); + if (Config.DOMAIN_LOCK != null) { + this.mAccountJidLabel.setText(R.string.username); + this.mAccountJid.setHint(R.string.username_hint); + } this.mPassword = (EditText) findViewById(R.id.account_password); this.mPassword.addTextChangedListener(this.mTextWatcher); this.mPasswordConfirm = (EditText) findViewById(R.id.account_password_confirm); @@ -301,6 +407,17 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mAvatar.setOnClickListener(this.mAvatarClickListener); this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new); this.mStats = (LinearLayout) findViewById(R.id.stats); + this.mBatteryOptimizations = (RelativeLayout) findViewById(R.id.battery_optimization); + this.mDisableBatterOptimizations = (Button) findViewById(R.id.batt_op_disable); + this.mDisableBatterOptimizations.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + Uri uri = Uri.parse("package:"+getPackageName()); + intent.setData(uri); + startActivityForResult(intent,REQUEST_BATTERY_OP); + } + }); this.mSessionEst = (TextView) findViewById(R.id.session_est); this.mServerInfoRosterVersion = (TextView) findViewById(R.id.server_info_roster_version); this.mServerInfoCarbons = (TextView) findViewById(R.id.server_info_carbons); @@ -309,9 +426,24 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mServerInfoBlocking = (TextView) findViewById(R.id.server_info_blocking); this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm); this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep); + this.mServerInfoHttpUpload = (TextView) findViewById(R.id.server_info_http_upload); + this.mPushRow = (TableRow) findViewById(R.id.push_row); + this.mServerInfoPush = (TextView) findViewById(R.id.server_info_push); 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.mAxolotlFingerprint = (TextView) findViewById(R.id.axolotl_fingerprint); + this.mAxolotlFingerprintBox = (RelativeLayout) findViewById(R.id.axolotl_fingerprint_box); + this.mAxolotlFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_axolotl_to_clipboard); + this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_axolotl_key); + this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card); + this.keys = (LinearLayout) findViewById(R.id.other_device_keys); + this.mNamePort = (LinearLayout) findViewById(R.id.name_port); + this.mHostname = (EditText) findViewById(R.id.hostname); + this.mHostname.addTextChangedListener(mTextWatcher); + this.mPort = (EditText) findViewById(R.id.port); + this.mPort.setText("5222"); + this.mPort.addTextChangedListener(mTextWatcher); this.mSaveButton = (Button) findViewById(R.id.save_button); this.mCancelButton = (Button) findViewById(R.id.cancel_button); this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener); @@ -320,7 +452,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() { @Override public void onCheckedChanged(final CompoundButton buttonView, - final boolean isChecked) { + final boolean isChecked) { if (isChecked) { mPasswordConfirm.setVisibility(View.VISIBLE); } else { @@ -330,6 +462,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } }; this.mRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword); + if (Config.DISALLOW_REGISTRATION_IN_UI) { + this.mRegisterNew.setVisibility(View.GONE); + } } @Override @@ -340,6 +475,12 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list); final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more); final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server); + final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices); + final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate); + final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs); + + renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null); + if (mAccount != null && mAccount.isOnlineAndConnected()) { if (!mAccount.getXmppConnection().getFeatures().blocking()) { showBlocklist.setVisible(false); @@ -347,11 +488,18 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate if (!mAccount.getXmppConnection().getFeatures().register()) { changePassword.setVisible(false); } + mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam()); + Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds(); + if (otherDevices == null || otherDevices.isEmpty()) { + clearDevices.setVisible(false); + } } else { showQrCode.setVisible(false); showBlocklist.setVisible(false); showMoreInfo.setVisible(false); changePassword.setVisible(false); + clearDevices.setVisible(false); + mamPrefs.setVisible(false); } return true; } @@ -365,7 +513,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } catch (final InvalidJidException | NullPointerException ignored) { this.jidToEdit = null; } - if (this.jidToEdit != null) { + this.mInitMode = getIntent().getBooleanExtra("init", false) || this.jidToEdit == null; + this.messageFingerprint = getIntent().getStringExtra("fingerprint"); + if (!mInitMode) { this.mRegisterNew.setVisibility(View.GONE); if (getActionBar() != null) { getActionBar().setTitle(getString(R.string.account_details)); @@ -377,16 +527,24 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } } } + SharedPreferences preferences = getPreferences(); + this.mShowOptions = preferences.getBoolean("show_connection_options", false); + this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); } @Override protected void onBackendConnected() { - final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this, - android.R.layout.simple_list_item_1, - xmppConnectionService.getKnownHosts()); if (this.jidToEdit != null) { this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit); - updateAccountInformation(true); + if (this.mAccount != null) { + if (this.mAccount.getPrivateKeyAlias() != null) { + this.mPassword.setHint(R.string.authenticate_with_certificate); + if (this.mInitMode) { + this.mPassword.requestFocus(); + } + } + updateAccountInformation(true); + } } else if (this.xmppConnectionService.getAccounts().size() == 0) { if (getActionBar() != null) { getActionBar().setDisplayHomeAsUpEnabled(false); @@ -396,8 +554,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mCancelButton.setEnabled(false); this.mCancelButton.setTextColor(getSecondaryTextColor()); } - this.mAccountJid.setAdapter(mKnownHostsAdapter); + if (Config.DOMAIN_LOCK == null) { + final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this, + android.R.layout.simple_list_item_1, + xmppConnectionService.getKnownHosts()); + this.mAccountJid.setAdapter(mKnownHostsAdapter); + } updateSaveButton(); + invalidateOptionsMenu(); } @Override @@ -405,7 +569,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate switch (item.getItemId()) { case R.id.action_show_block_list: final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class); - showBlocklistIntent.putExtra("account", mAccount.getJid().toString()); + showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString()); startActivity(showBlocklistIntent); break; case R.id.action_server_info_show_more: @@ -414,19 +578,50 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate break; case R.id.action_change_password_on_server: final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class); - changePasswordIntent.putExtra("account", mAccount.getJid().toString()); + changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString()); startActivity(changePasswordIntent); break; + case R.id.action_mam_prefs: + editMamPrefs(); + break; + case R.id.action_clear_devices: + showWipePepDialog(); + break; + case R.id.action_renew_certificate: + renewCertificate(); + break; } return super.onOptionsItemSelected(item); } + private void renewCertificate() { + KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null); + } + + @Override + public void alias(String alias) { + if (alias != null) { + xmppConnectionService.updateKeyInAccount(mAccount, alias); + } + } + private void updateAccountInformation(boolean init) { if (init) { - this.mAccountJid.setText(this.mAccount.getJid().toBareJid().toString()); + this.mAccountJid.getEditableText().clear(); + if (Config.DOMAIN_LOCK != null) { + this.mAccountJid.getEditableText().append(this.mAccount.getJid().getLocalpart()); + } else { + this.mAccountJid.getEditableText().append(this.mAccount.getJid().toBareJid().toString()); + } this.mPassword.setText(this.mAccount.getPassword()); + this.mHostname.setText(""); + this.mHostname.getEditableText().append(this.mAccount.getHostname()); + this.mPort.setText(""); + this.mPort.getEditableText().append(String.valueOf(this.mAccount.getPort())); + this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); + } - if (this.jidToEdit != null) { + if (!mInitMode) { this.mAvatar.setVisibility(View.VISIBLE); this.mAvatar.setImageBitmap(AvatarService.getInstance().get(this.mAccount, getPixel(72))); } @@ -449,8 +644,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate detailsAccountJid.setText(this.mAccount.getJid().toBareJid().toString()); } this.mStats.setVisibility(View.VISIBLE); + this.mBatteryOptimizations.setVisibility(showBatteryOptimizationWarning() ? View.VISIBLE : View.GONE); this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection() - .getLastSessionEstablished())); + .getLastSessionEstablished())); Features features = this.mAccount.getXmppConnection().getFeatures(); if (features.rosterVersioning()) { this.mServerInfoRosterVersion.setText(R.string.server_info_available); @@ -461,7 +657,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mServerInfoCarbons.setText(R.string.server_info_available); } else { this.mServerInfoCarbons - .setText(R.string.server_info_unavailable); + .setText(R.string.server_info_unavailable); } if (features.mam()) { this.mServerInfoMam.setText(R.string.server_info_available); @@ -484,43 +680,286 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mServerInfoSm.setText(R.string.server_info_unavailable); } if (features.pep()) { - this.mServerInfoPep.setText(R.string.server_info_available); + AxolotlService axolotlService = this.mAccount.getAxolotlService(); + if (axolotlService != null && axolotlService.isPepBroken()) { + this.mServerInfoPep.setText(R.string.server_info_broken); + } else { + this.mServerInfoPep.setText(R.string.server_info_available); + } } else { this.mServerInfoPep.setText(R.string.server_info_unavailable); } - final String fingerprint = this.mAccount.getOtrFingerprint(); - if (fingerprint != null) { + if (features.httpUpload()) { + this.mServerInfoHttpUpload.setText(R.string.server_info_available); + } else { + this.mServerInfoHttpUpload.setText(R.string.server_info_unavailable); + } + + this.mPushRow.setVisibility(xmppConnectionService.getPushManagementService().isStub() ? View.GONE : View.VISIBLE); + + if (xmppConnectionService.getPushManagementService().available(mAccount)) { + this.mServerInfoPush.setText(R.string.server_info_available); + } else { + this.mServerInfoPush.setText(R.string.server_info_unavailable); + } + final String otrFingerprint = this.mAccount.getOtrFingerprint(); + if (otrFingerprint != null) { this.mOtrFingerprintBox.setVisibility(View.VISIBLE); - this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(fingerprint)); + this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(otrFingerprint)); this.mOtrFingerprintToClipboardButton - .setVisibility(View.VISIBLE); + .setVisibility(View.VISIBLE); this.mOtrFingerprintToClipboardButton - .setOnClickListener(new View.OnClickListener() { + .setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View v) { + @Override + public void onClick(final View v) { - if (copyTextToClipboard(fingerprint, R.string.otr_fingerprint)) { - Toast.makeText( - EditAccountActivity.this, - R.string.toast_message_otr_fingerprint, - Toast.LENGTH_SHORT).show(); + if (copyTextToClipboard(otrFingerprint, R.string.otr_fingerprint)) { + Toast.makeText( + EditAccountActivity.this, + R.string.toast_message_otr_fingerprint, + Toast.LENGTH_SHORT).show(); + } } - } - }); + }); } else { this.mOtrFingerprintBox.setVisibility(View.GONE); } + final String axolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint(); + if (axolotlFingerprint != null) { + this.mAxolotlFingerprintBox.setVisibility(View.VISIBLE); + this.mAxolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(axolotlFingerprint.substring(2))); + this.mAxolotlFingerprintToClipboardButton + .setVisibility(View.VISIBLE); + this.mAxolotlFingerprintToClipboardButton + .setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(final View v) { + + if (copyTextToClipboard(axolotlFingerprint.substring(2), R.string.omemo_fingerprint)) { + Toast.makeText( + EditAccountActivity.this, + R.string.toast_message_omemo_fingerprint, + Toast.LENGTH_SHORT).show(); + } + } + }); + if (Config.SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON) { + this.mRegenerateAxolotlKeyButton + .setVisibility(View.VISIBLE); + this.mRegenerateAxolotlKeyButton + .setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(final View v) { + showRegenerateAxolotlKeyDialog(); + } + }); + } + } else { + this.mAxolotlFingerprintBox.setVisibility(View.GONE); + } + final String ownFingerprint = mAccount.getAxolotlService().getOwnFingerprint(); + boolean hasKeys = false; + keys.removeAllViews(); + for (final String fingerprint : mAccount.getAxolotlService().getFingerprintsForOwnSessions()) { + if (ownFingerprint.equals(fingerprint)) { + continue; + } + boolean highlight = fingerprint.equals(messageFingerprint); + hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight, null); + } + if (hasKeys) { + keysCard.setVisibility(View.VISIBLE); + } else { + keysCard.setVisibility(View.GONE); + } } else { if (this.mAccount.errorStatus()) { - this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId())); + final EditText errorTextField; + if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) { + errorTextField = this.mPassword; + } else if (mShowOptions + && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND + && this.mHostname.getText().length() > 0) { + errorTextField = this.mHostname; + } else { + errorTextField = this.mAccountJid; + } + errorTextField.setError(getString(this.mAccount.getStatus().getReadableId())); if (init || !accountInfoEdited()) { - this.mAccountJid.requestFocus(); + errorTextField.requestFocus(); } } else { this.mAccountJid.setError(null); + this.mPassword.setError(null); + this.mHostname.setError(null); } this.mStats.setVisibility(View.GONE); } } + + public void showRegenerateAxolotlKeyDialog() { + Builder builder = new Builder(this); + builder.setTitle("Regenerate Key"); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage("Are you sure you want to regenerate your Identity Key? (This will also wipe all established sessions and contact Identity Keys)"); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton("Yes", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mAccount.getAxolotlService().regenerateKeys(false); + } + }); + builder.create().show(); + } + + public void showWipePepDialog() { + Builder builder = new Builder(this); + builder.setTitle(getString(R.string.clear_other_devices)); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage(getString(R.string.clear_other_devices_desc)); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.accept), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mAccount.getAxolotlService().wipeOtherPepDevices(); + } + }); + builder.create().show(); + } + + private void editMamPrefs() { + this.mFetchingMamPrefsToast = Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG); + this.mFetchingMamPrefsToast.show(); + xmppConnectionService.fetchMamPreferences(mAccount, this); + } + + @Override + public void onKeyStatusUpdated(AxolotlService.FetchStatus report) { + refreshUi(); + } + + @Override + public void onCaptchaRequested(final Account account, final String id, final Data data, + final Bitmap captcha) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + final ImageView view = new ImageView(this); + final LinearLayout layout = new LinearLayout(this); + final EditText input = new EditText(this); + + view.setImageBitmap(captcha); + view.setScaleType(ImageView.ScaleType.FIT_CENTER); + + input.setHint(getString(R.string.captcha_hint)); + + layout.setOrientation(LinearLayout.VERTICAL); + layout.addView(view); + layout.addView(input); + + builder.setTitle(getString(R.string.captcha_required)); + builder.setView(layout); + + builder.setPositiveButton(getString(R.string.ok), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String rc = input.getText().toString(); + data.put("username", account.getUsername()); + data.put("password", account.getPassword()); + data.put("ocr", rc); + data.submit(); + + if (xmppConnectionServiceBound) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket( + account, id, data); + } + } + }); + builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (xmppConnectionService != null) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); + } + } + }); + + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + if (xmppConnectionService != null) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); + } + } + }); + + runOnUiThread(new Runnable() { + @Override + public void run() { + if ((mCaptchaDialog != null) && mCaptchaDialog.isShowing()) { + mCaptchaDialog.dismiss(); + } + mCaptchaDialog = builder.create(); + mCaptchaDialog.show(); + } + }); + } + + public void onShowErrorToast(final int resId) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onPreferencesFetched(final Element prefs) { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (mFetchingMamPrefsToast != null) { + mFetchingMamPrefsToast.cancel(); + } + AlertDialog.Builder builder = new Builder(EditAccountActivity.this); + builder.setTitle(R.string.server_side_mam_prefs); + String defaultAttr = prefs.getAttribute("default"); + final List<String> defaults = Arrays.asList("never", "roster", "always"); + final AtomicInteger choice = new AtomicInteger(Math.max(0,defaults.indexOf(defaultAttr))); + builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + choice.set(which); + } + }); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + prefs.setAttribute("default",defaults.get(choice.get())); + xmppConnectionService.pushMamPreferences(mAccount, prefs); + } + }); + builder.create().show(); + } + }); + } + + @Override + public void onPreferencesFetchFailed() { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (mFetchingMamPrefsToast != null) { + mFetchingMamPrefsToast.cancel(); + } + Toast.makeText(EditAccountActivity.this,R.string.unable_to_fetch_mam_prefs,Toast.LENGTH_LONG).show(); + } + }); + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/EditMessage.java b/src/main/java/de/thedevstack/conversationsplus/ui/EditMessage.java index 5c2e6164..5664434f 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/EditMessage.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/EditMessage.java @@ -32,21 +32,32 @@ public class EditMessage extends EmojiconEditText { private boolean isUserTyping = false; + private boolean lastInputWasTab = false; + protected KeyboardListener keyboardListener; @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_ENTER) { + public boolean onKeyDown(int keyCode, KeyEvent e) { + if (keyCode == KeyEvent.KEYCODE_ENTER && !e.isShiftPressed()) { + lastInputWasTab = false; if (keyboardListener != null && keyboardListener.onEnterPressed()) { return true; } + } else if (keyCode == KeyEvent.KEYCODE_TAB && !e.isAltPressed() && !e.isCtrlPressed()) { + if (keyboardListener != null && keyboardListener.onTabPressed(this.lastInputWasTab)) { + lastInputWasTab = true; + return true; + } + } else { + lastInputWasTab = false; } - return super.onKeyDown(keyCode, event); + return super.onKeyDown(keyCode, e); } @Override public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { super.onTextChanged(text,start,lengthBefore,lengthAfter); + lastInputWasTab = false; if (this.mTypingHandler != null && this.keyboardListener != null) { this.mTypingHandler.removeCallbacks(mTypingTimeout); this.mTypingHandler.postDelayed(mTypingTimeout, Config.TYPING_TIMEOUT * 1000); @@ -69,10 +80,11 @@ public class EditMessage extends EmojiconEditText { } public interface KeyboardListener { - public boolean onEnterPressed(); - public void onTypingStarted(); - public void onTypingStopped(); - public void onTextDeleted(); + boolean onEnterPressed(); + void onTypingStarted(); + void onTypingStopped(); + void onTextDeleted(); + boolean onTabPressed(boolean repeated); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/EnterJidDialog.java b/src/main/java/de/thedevstack/conversationsplus/ui/EnterJidDialog.java new file mode 100644 index 00000000..9e52d390 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/ui/EnterJidDialog.java @@ -0,0 +1,134 @@ +package de.thedevstack.conversationsplus.ui; + +import android.app.AlertDialog; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.Spinner; +import android.widget.TextView; + +import java.util.List; + +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.ui.adapter.KnownHostsAdapter; +import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; + +public class EnterJidDialog { + public interface OnEnterJidDialogPositiveListener { + boolean onEnterJidDialogPositive(Jid account, Jid contact) throws EnterJidDialog.JidError; + } + + public static class JidError extends Exception { + final String msg; + + public JidError(final String msg) { + this.msg = msg; + } + + public String toString() { + return msg; + } + } + + protected final AlertDialog dialog; + protected View.OnClickListener dialogOnClick; + protected OnEnterJidDialogPositiveListener listener = null; + + public EnterJidDialog( + final Context context, List<String> knownHosts, final List<String> activatedAccounts, + final String title, final String positiveButton, + final String prefilledJid, final String account, boolean allowEditJid + ) { + final boolean lock = Config.LOCK_DOMAINS_IN_CONVERSATIONS && Config.DOMAIN_LOCK != null; + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(title); + View dialogView = LayoutInflater.from(context).inflate(R.layout.enter_jid_dialog, null); + final TextView jabberIdDesc = (TextView) dialogView.findViewById(R.id.jabber_id); + jabberIdDesc.setText(lock ? R.string.username : R.string.account_settings_jabber_id); + final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account); + final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid); + if (!lock) { + jid.setAdapter(new KnownHostsAdapter(context, android.R.layout.simple_list_item_1, knownHosts)); + } + if (prefilledJid != null) { + jid.append(prefilledJid); + if (!allowEditJid) { + jid.setFocusable(false); + jid.setFocusableInTouchMode(false); + jid.setClickable(false); + jid.setCursorVisible(false); + } + } + + jid.setHint(Config.LOCK_DOMAINS_IN_CONVERSATIONS && Config.DOMAIN_LOCK != null ? R.string.username_hint : R.string.account_settings_example_jabber_id); + + if (account == null) { + StartConversationActivity.populateAccountSpinner(context, activatedAccounts, spinner); + } else { + ArrayAdapter<String> adapter = new ArrayAdapter<>(context, + android.R.layout.simple_spinner_item, + new String[] { account }); + spinner.setEnabled(false); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + } + + builder.setView(dialogView); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(positiveButton, null); + this.dialog = builder.create(); + + this.dialogOnClick = new View.OnClickListener() { + @Override + public void onClick(final View v) { + final Jid accountJid; + if (!spinner.isEnabled() && account == null) { + return; + } + try { + if (Config.DOMAIN_LOCK != null) { + accountJid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null); + } else { + accountJid = Jid.fromString((String) spinner.getSelectedItem()); + } + } catch (final InvalidJidException e) { + return; + } + final Jid contactJid; + try { + if (lock) { + contactJid = Jid.fromParts(jid.getText().toString(), Config.DOMAIN_LOCK, null); + } else { + contactJid = Jid.fromString(jid.getText().toString()); + } + } catch (final InvalidJidException e) { + jid.setError(context.getString(lock ? R.string.invalid_username : R.string.invalid_jid)); + return; + } + + if(listener != null) { + try { + if(listener.onEnterJidDialogPositive(accountJid, contactJid)) { + dialog.dismiss(); + } + } catch(JidError error) { + jid.setError(error.toString()); + } + } + } + }; + } + + public void setOnEnterJidDialogPositiveListener(OnEnterJidDialogPositiveListener listener) { + this.listener = listener; + } + + public void show() { + this.dialog.show(); + this.dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this.dialogOnClick); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ExportLogsPreference.java b/src/main/java/de/thedevstack/conversationsplus/ui/ExportLogsPreference.java new file mode 100644 index 00000000..eabb7518 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ExportLogsPreference.java @@ -0,0 +1,29 @@ +package de.thedevstack.conversationsplus.ui; + +import android.content.Context; +import android.content.Intent; +import android.preference.Preference; +import android.util.AttributeSet; + +import de.thedevstack.conversationsplus.services.ExportLogsService; + +public class ExportLogsPreference extends Preference { + + public ExportLogsPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public ExportLogsPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ExportLogsPreference(Context context) { + super(context); + } + + protected void onClick() { + final Intent startIntent = new Intent(getContext(), ExportLogsService.class); + getContext().startService(startIntent); + super.onClick(); + } +}
\ No newline at end of file diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ManageAccountActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ManageAccountActivity.java index 0820ec8b..431111f5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ManageAccountActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ManageAccountActivity.java @@ -1,34 +1,54 @@ package de.thedevstack.conversationsplus.ui; -import java.util.ArrayList; -import java.util.List; - -import de.thedevstack.conversationsplus.R; -import de.thedevstack.conversationsplus.entities.Account; -import de.thedevstack.conversationsplus.services.XmppConnectionService.OnAccountUpdate; -import de.thedevstack.conversationsplus.ui.adapter.AccountAdapter; +import android.app.ActionBar; import android.app.AlertDialog; +import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; +import android.security.KeyChain; +import android.security.KeyChainAliasCallback; +import android.util.Pair; import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; 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; +import android.widget.Toast; + +import org.openintents.openpgp.util.OpenPgpApi; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.services.XmppConnectionService; +import de.thedevstack.conversationsplus.services.XmppConnectionService.OnAccountUpdate; +import de.thedevstack.conversationsplus.ui.adapter.AccountAdapter; +import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; -public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate { +public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated { + + private final String STATE_SELECTED_ACCOUNT = "selected_account"; protected Account selectedAccount = null; + protected Jid selectedAccountJid = null; protected final List<Account> accountList = new ArrayList<>(); protected ListView accountListView; protected AccountAdapter mAccountAdapter; + protected AtomicBoolean mInvokedAddAccount = new AtomicBoolean(false); + + protected Pair<Integer, Intent> mPostponedActivityResult = null; @Override public void onAccountUpdate() { @@ -41,6 +61,11 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda accountList.clear(); accountList.addAll(xmppConnectionService.getAccounts()); } + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setHomeButtonEnabled(this.accountList.size() > 0); + actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0); + } invalidateOptionsMenu(); mAccountAdapter.notifyDataSetChanged(); } @@ -52,6 +77,17 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda setContentView(R.layout.manage_accounts); + if (savedInstanceState != null) { + String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT); + if (jid != null) { + try { + this.selectedAccountJid = Jid.fromString(jid); + } catch (InvalidJidException e) { + this.selectedAccountJid = null; + } + } + } + accountListView = (ListView) findViewById(R.id.account_list); this.mAccountAdapter = new AccountAdapter(this, accountList); accountListView.setAdapter(this.mAccountAdapter); @@ -59,7 +95,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda @Override public void onItemClick(AdapterView<?> arg0, View view, - int position, long arg3) { + int position, long arg3) { switchToAccount(accountList.get(position)); } }); @@ -67,12 +103,19 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { + public void onSaveInstanceState(final Bundle savedInstanceState) { + if (selectedAccount != null) { + savedInstanceState.putString(STATE_SELECTED_ACCOUNT, selectedAccount.getJid().toBareJid().toString()); + } + super.onSaveInstanceState(savedInstanceState); + } + + @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; + 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); @@ -80,21 +123,42 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false); } else { menu.findItem(R.id.mgmt_account_enable).setVisible(false); + menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(Config.supportOpenPgp()); } menu.setHeaderTitle(this.selectedAccount.getJid().toBareJid().toString()); } @Override void onBackendConnected() { - this.accountList.clear(); - this.accountList.addAll(xmppConnectionService.getAccounts()); - mAccountAdapter.notifyDataSetChanged(); + if (selectedAccountJid != null) { + this.selectedAccount = xmppConnectionService.findAccountByJid(selectedAccountJid); + } + refreshUiReal(); + if (this.mPostponedActivityResult != null) { + this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); + } + if (Config.X509_VERIFICATION && this.accountList.size() == 0) { + if (mInvokedAddAccount.compareAndSet(false, true)) { + addAccountFromKey(); + } + } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.manageaccounts, menu); MenuItem enableAll = menu.findItem(R.id.action_enable_all); + MenuItem addAccount = menu.findItem(R.id.action_add_account); + MenuItem addAccountWithCertificate = menu.findItem(R.id.action_add_account_with_cert); + + if (Config.X509_VERIFICATION) { + addAccount.setVisible(false); + addAccountWithCertificate.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + } else { + addAccount.setVisible(!Config.SINGLE_ACCOUNT); + } + addAccountWithCertificate.setVisible(!Config.SINGLE_ACCOUNT); + if (!accountsLeftToEnable()) { enableAll.setVisible(false); } @@ -108,23 +172,23 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda @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); - return true; - default: - return super.onContextItemSelected(item); + 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); + return true; + default: + return super.onContextItemSelected(item); } } @@ -141,6 +205,9 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda case R.id.action_enable_all: enableAllAccounts(); break; + case R.id.action_add_account_with_cert: + addAccountFromKey(); + break; default: break; } @@ -153,9 +220,9 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda Intent contactsIntent = new Intent(this, StartConversationActivity.class); contactsIntent.setFlags( - // if activity exists in stack, pop the stack and go back to it + // 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 + // otherwise, make a new task for it Intent.FLAG_ACTIVITY_NEW_TASK | // don't use the new activity animation; finish // animation runs instead @@ -176,10 +243,18 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } } + private void addAccountFromKey() { + try { + KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG).show(); + } + } + private void publishAvatar(Account account) { Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class); - intent.putExtra("account", account.getJid().toString()); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().toString()); startActivity(intent); } @@ -192,7 +267,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } } } - for(Account account : list) { + for (Account account : list) { disableAccount(account); } } @@ -228,7 +303,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda } } } - for(Account account : list) { + for (Account account : list) { enableAccount(account); } } @@ -245,7 +320,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda private void publishOpenPGPPublicKey(Account account) { if (ManageAccountActivity.this.hasPgp()) { - announcePgp(account, null); + choosePgpSignId(selectedAccount); } else { this.showInstallPgpDialog(); } @@ -273,9 +348,43 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda 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); + if (xmppConnectionServiceBound) { + if (requestCode == REQUEST_CHOOSE_PGP_ID) { + if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) { + selectedAccount.setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID)); + announcePgp(selectedAccount, null); + } else { + choosePgpSignId(selectedAccount); + } + } else if (requestCode == REQUEST_ANNOUNCE_PGP) { + announcePgp(selectedAccount, null); + } + this.mPostponedActivityResult = null; + } else { + this.mPostponedActivityResult = new Pair<>(requestCode, data); } } } + + @Override + public void alias(String alias) { + if (alias != null) { + xmppConnectionService.createAccountFromKey(alias, this); + } + } + + @Override + public void onAccountCreated(Account account) { + switchToAccount(account, true); + } + + @Override + public void informUser(final int r) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ManageAccountActivity.this, r, Toast.LENGTH_LONG).show(); + } + }); + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/PublishProfilePictureActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/PublishProfilePictureActivity.java index 2045f001..9b827861 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/PublishProfilePictureActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/PublishProfilePictureActivity.java @@ -2,7 +2,9 @@ package de.thedevstack.conversationsplus.ui; import android.app.PendingIntent; import android.content.Intent; +import android.content.pm.PackageManager; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; import android.view.View; @@ -13,28 +15,34 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import com.soundcloud.android.crop.Crop; + +import java.io.File; +import java.io.FileNotFoundException; + +import de.thedevstack.conversationsplus.utils.ImageUtil; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.services.AvatarService; -import de.thedevstack.conversationsplus.utils.ImageUtil; +import de.thedevstack.conversationsplus.utils.ExifHelper; +import de.thedevstack.conversationsplus.utils.FileUtils; import de.thedevstack.conversationsplus.utils.PhoneHelper; -import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; -import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.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 OnLongClickListener backToDefaultListener = new OnLongClickListener() { @Override @@ -44,8 +52,6 @@ public class PublishProfilePictureActivity extends XmppActivity { return true; } }; - private Account account; - private boolean support = false; private boolean mInitialAccountSetup; private UiCallback<Avatar> avatarPublication = new UiCallback<Avatar>() { @@ -58,7 +64,7 @@ public class PublishProfilePictureActivity extends XmppActivity { if (mInitialAccountSetup) { Intent intent = new Intent(getApplicationContext(), StartConversationActivity.class); - intent.putExtra("init",true); + intent.putExtra("init", true); startActivity(intent); } Toast.makeText(PublishProfilePictureActivity.this, @@ -130,26 +136,60 @@ public class PublishProfilePictureActivity extends XmppActivity { @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); + if (hasStoragePermission(REQUEST_CHOOSE_FILE)) { + chooseAvatar(); + } + } }); this.defaultUri = PhoneHelper.getSefliUri(getApplicationContext()); } + private void chooseAvatar() { + 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); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + if (grantResults.length > 0) + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == REQUEST_CHOOSE_FILE) { + chooseAvatar(); + } + } else { + Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); + } + } + @Override - protected void onActivityResult(int requestCode, int resultCode, - final Intent data) { + 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) { + switch (requestCode) { + case REQUEST_CHOOSE_FILE: + Uri source = data.getData(); + String original = FileUtils.getPath(this, source); + if (original != null) { + source = Uri.parse("file://"+original); + } + Uri destination = Uri.fromFile(new File(getCacheDir(), "croppedAvatar")); + final int size = getPixel(192); + Crop.of(source, destination).asSquare().withMaxSize(size, size).start(this); + break; + case Crop.REQUEST_CROP: + this.avatarUri = Uri.fromFile(new File(getCacheDir(), "croppedAvatar")); loadImageIntoPreview(this.avatarUri); + break; + } + } else { + if (requestCode == Crop.REQUEST_CROP && data != null) { + Throwable throwable = Crop.getError(data); + if (throwable != null && throwable instanceof OutOfMemoryError) { + Toast.makeText(this,R.string.selection_too_large, Toast.LENGTH_SHORT).show(); } } } @@ -157,63 +197,75 @@ public class PublishProfilePictureActivity extends XmppActivity { @Override protected void onBackendConnected() { - if (getIntent() != null) { - Jid jid; - try { - jid = Jid.fromString(getIntent().getStringExtra("account")); - } catch (InvalidJidException e) { - jid = null; - } - if (jid != null) { - this.account = xmppConnectionService.findAccountByJid(jid); - if (this.account.getXmppConnection() != null) { - this.support = this.account.getXmppConnection().getFeatures().pep(); - } - if (this.avatarUri == null) { - if (this.account.getAvatar() != null - || this.defaultUri == null) { - this.avatar.setImageBitmap(AvatarService.getInstance().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); - } + this.account = extractAccount(getIntent()); + if (this.account != null) { + if (this.account.getXmppConnection() != null) { + this.support = this.account.getXmppConnection().getFeatures().pep(); + } + if (this.avatarUri == null) { + if (this.account.getAvatar() != null + || this.defaultUri == null) { + this.avatar.setImageBitmap(AvatarService.getInstance().get(account, getPixel(192))); + if (this.defaultUri != null) { + this.avatar + .setOnLongClickListener(this.backToDefaultListener); } else { - this.avatarUri = this.defaultUri; - loadImageIntoPreview(this.defaultUri); this.secondaryHint.setVisibility(View.INVISIBLE); } + if (!support) { + this.hintOrWarning + .setTextColor(getWarningTextColor()); + this.hintOrWarning + .setText(R.string.error_publish_avatar_no_server_support); + } } else { - loadImageIntoPreview(avatarUri); + this.avatarUri = this.defaultUri; + loadImageIntoPreview(this.defaultUri); + this.secondaryHint.setVisibility(View.INVISIBLE); } - this.accountTextView.setText(this.account.getJid().toBareJid().toString()); + } else { + loadImageIntoPreview(avatarUri); + } + String account; + if (Config.DOMAIN_LOCK != null) { + account = this.account.getJid().getLocalpart(); + } else { + account = this.account.getJid().toBareJid().toString(); } + this.accountTextView.setText(account); } - } @Override protected void onStart() { super.onStart(); if (getIntent() != null) { - this.mInitialAccountSetup = getIntent().getBooleanExtra("setup", - false); + this.mInitialAccountSetup = getIntent().getBooleanExtra("setup", false); } if (this.mInitialAccountSetup) { this.cancelButton.setText(R.string.skip); } } + private Bitmap loadScaledBitmap(Uri uri, int reqSize) throws FileNotFoundException { + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(getContentResolver().openInputStream(uri), null, options); + int rotation = ExifHelper.getOrientation(getContentResolver().openInputStream(uri)); + options.inSampleSize = ImageUtil.calcSampleSize(options, reqSize); + options.inJustDecodeBounds = false; + Bitmap bm = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri), null, options); + return ImageUtil.rotate(bm,rotation); + } + protected void loadImageIntoPreview(Uri uri) { - Bitmap bm = ImageUtil.cropCenterSquare(uri, 384); + Bitmap bm = null; + try { + bm = loadScaledBitmap(uri, getPixel(192)); + } catch (Exception e) { + e.printStackTrace(); + } + if (bm == null) { disablePublishButton(); this.hintOrWarning.setTextColor(getWarningTextColor()); @@ -252,4 +304,7 @@ public class PublishProfilePictureActivity extends XmppActivity { this.publishButton.setTextColor(getSecondaryTextColor()); } + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/SettingsActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/SettingsActivity.java index c364ff00..e8c9587b 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/SettingsActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/SettingsActivity.java @@ -1,20 +1,5 @@ package de.thedevstack.conversationsplus.ui; -import java.security.KeyStoreException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Locale; - -import de.thedevstack.conversationsplus.ConversationsPlusPreferences; -import de.tzur.conversations.Settings; -import de.duenndns.ssl.MemorizingTrustManager; - -import de.thedevstack.conversationsplus.R; -import de.thedevstack.conversationsplus.entities.Account; -import de.thedevstack.conversationsplus.xmpp.XmppConnection; -import github.ankushsachdeva.emojicon.EmojiconHandler; - import android.app.AlertDialog; import android.app.FragmentManager; import android.content.DialogInterface; @@ -24,9 +9,23 @@ import android.os.Build; import android.os.Bundle; import android.preference.ListPreference; import android.preference.Preference; +import android.preference.PreferenceCategory; import android.preference.PreferenceManager; import android.widget.Toast; +import java.security.KeyStoreException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; + +import de.duenndns.ssl.MemorizingTrustManager; +import de.tzur.conversations.Settings; +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.xmpp.XmppConnection; +import github.ankushsachdeva.emojicon.EmojiconHandler; + public class SettingsActivity extends XmppActivity implements OnSharedPreferenceChangeListener { private SettingsFragment mSettingsFragment; @@ -63,65 +62,71 @@ public class SettingsActivity extends XmppActivity implements final Preference removeCertsPreference = mSettingsFragment.findPreference("remove_trusted_certificates"); removeCertsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - final MemorizingTrustManager mtm = xmppConnectionService.getMemorizingTrustManager(); - final ArrayList<String> aliases = Collections.list(mtm.getCertificates()); - if (aliases.size() == 0) { - displayToast(getString(R.string.toast_no_trusted_certs)); - return true; - } - final ArrayList selectedItems = new ArrayList<Integer>(); - final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(SettingsActivity.this); - dialogBuilder.setTitle(getResources().getString(R.string.dialog_manage_certs_title)); - dialogBuilder.setMultiChoiceItems(aliases.toArray(new CharSequence[aliases.size()]), null, - new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int indexSelected, - boolean isChecked) { - if (isChecked) { - selectedItems.add(indexSelected); - } else if (selectedItems.contains(indexSelected)) { - selectedItems.remove(Integer.valueOf(indexSelected)); - } - if (selectedItems.size() > 0) - ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true); - else { - ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false); - } - } - }); - - dialogBuilder.setPositiveButton( - getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - int count = selectedItems.size(); - if (count > 0) { - for (int i = 0; i < count; i++) { - try { - Integer item = Integer.valueOf(selectedItems.get(i).toString()); - String alias = aliases.get(item); - mtm.deleteCertificate(alias); - } catch (KeyStoreException e) { - e.printStackTrace(); - displayToast("Error: " + e.getLocalizedMessage()); - } - } - if (xmppConnectionServiceBound) { - reconnectAccounts(); - } - displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count)); - } - } - }); - dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null); - AlertDialog removeCertsDialog = dialogBuilder.create(); - removeCertsDialog.show(); - removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); - return true; - } - }); + @Override + public boolean onPreferenceClick(Preference preference) { + final MemorizingTrustManager mtm = xmppConnectionService.getMemorizingTrustManager(); + final ArrayList<String> aliases = Collections.list(mtm.getCertificates()); + if (aliases.size() == 0) { + displayToast(getString(R.string.toast_no_trusted_certs)); + return true; + } + final ArrayList selectedItems = new ArrayList<Integer>(); + final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(SettingsActivity.this); + dialogBuilder.setTitle(getResources().getString(R.string.dialog_manage_certs_title)); + dialogBuilder.setMultiChoiceItems(aliases.toArray(new CharSequence[aliases.size()]), null, + new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int indexSelected, + boolean isChecked) { + if (isChecked) { + selectedItems.add(indexSelected); + } else if (selectedItems.contains(indexSelected)) { + selectedItems.remove(Integer.valueOf(indexSelected)); + } + if (selectedItems.size() > 0) + ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true); + else { + ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false); + } + } + }); + + dialogBuilder.setPositiveButton( + getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int count = selectedItems.size(); + if (count > 0) { + for (int i = 0; i < count; i++) { + try { + Integer item = Integer.valueOf(selectedItems.get(i).toString()); + String alias = aliases.get(item); + mtm.deleteCertificate(alias); + } catch (KeyStoreException e) { + e.printStackTrace(); + displayToast("Error: " + e.getLocalizedMessage()); + } + } + if (xmppConnectionServiceBound) { + reconnectAccounts(); + } + displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count)); + } + } + }); + dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null); + AlertDialog removeCertsDialog = dialogBuilder.create(); + removeCertsDialog.show(); + removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); + return true; + } + }); + // Avoid appearence of setting to enable or disable omemo in screen + Preference omemoEnabledPreference = this.mSettingsFragment.findPreference("omemo_enabled"); + PreferenceCategory otherExpertSettingsGroup = (PreferenceCategory) this.mSettingsFragment.findPreference("other_expert_settings"); + if (null != omemoEnabledPreference && null != otherExpertSettingsGroup) { + otherExpertSettingsGroup.removePreference(omemoEnabledPreference); + } } @Override @@ -136,42 +141,40 @@ public class SettingsActivity extends XmppActivity implements String name) { // need to synchronize the settings class first Settings.synchronizeSettingsClassWithPreferences(preferences, name); - switch (name) { - case "resource": - String resource = ConversationsPlusPreferences.resource().toLowerCase(Locale.US); - if (xmppConnectionServiceBound) { - for (Account account : xmppConnectionService.getAccounts()) { - account.setResource(resource); - if (!account.isOptionSet(Account.OPTION_DISABLED)) { + if (name.equals("resource")) { + String resource = preferences.getString("resource", "mobile") + .toLowerCase(Locale.US); + if (xmppConnectionServiceBound) { + for (Account account : xmppConnectionService.getAccounts()) { + if (account.setResource(resource)) { + if (!account.isOptionSet(Account.OPTION_DISABLED)) { XmppConnection connection = account.getXmppConnection(); if (connection != null) { connection.resetStreamId(); } - xmppConnectionService.reconnectAccountInBackground(account); - } - } - } - break; - case "keep_foreground_service": - xmppConnectionService.toggleForegroundService(); - break; - case "confirm_messages": - if (xmppConnectionServiceBound) { - for (Account account : xmppConnectionService.getAccounts()) { - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - xmppConnectionService.sendPresence(account); + xmppConnectionService.reconnectAccountInBackground(account); } } } - break; - case "dont_trust_system_cas": - xmppConnectionService.updateMemorizingTrustmanager(); - reconnectAccounts(); - break; - case "parse_emoticons": - EmojiconHandler.setParseEmoticons(Settings.PARSE_EMOTICONS); - break; - } + } + } else if (name.equals("keep_foreground_service")) { + xmppConnectionService.toggleForegroundService(); + } else if (name.equals("confirm_messages_list") + || name.equals("xa_on_silent_mode") + || name.equals("away_when_screen_off")) { + if (xmppConnectionServiceBound) { + if (name.equals("away_when_screen_off")) { + xmppConnectionService.toggleScreenEventReceiver(); + } + xmppConnectionService.refreshAllPresences(); + } + } else if (name.equals("dont_trust_system_cas")) { + xmppConnectionService.updateMemorizingTrustmanager(); + reconnectAccounts(); + } else if ("parse_emoticons".equals(name)) { + EmojiconHandler.setParseEmoticons(Settings.PARSE_EMOTICONS); + } + } private void displayToast(final String msg) { @@ -191,4 +194,8 @@ public class SettingsActivity extends XmppActivity implements } } + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } + } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java index e73b7cbe..b0ee32f1 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java @@ -4,6 +4,7 @@ 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; @@ -17,25 +18,27 @@ import java.util.ArrayList; import java.util.List; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.ui.dialogs.UserDecisionDialog; +import de.thedevstack.conversationsplus.ui.listeners.ResizePictureUserDecisionListener; +import de.thedevstack.conversationsplus.ui.listeners.ShareWithResizePictureUserDecisionListener; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.ui.adapter.ConversationAdapter; -import de.thedevstack.conversationsplus.ui.dialogs.UserDecisionDialog; -import de.thedevstack.conversationsplus.ui.listeners.ResizePictureUserDecisionListener; -import de.thedevstack.conversationsplus.ui.listeners.ShareWithResizePictureUserDecisionListener; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; public class ShareWithActivity extends XmppActivity { - public class Share { + private class Share { public List<Uri> uris = new ArrayList<>(); public boolean image; public String account; public String contact; public String text; + public String uuid; } private Share share; @@ -43,6 +46,7 @@ public class ShareWithActivity extends XmppActivity { private static final int REQUEST_START_NEW_CONVERSATION = 0x0501; private ListView mListView; private List<Conversation> mConversations = new ArrayList<>(); + private Toast mToast; private UiCallback<Message> attachFileCallback = new UiCallback<Message>() { @@ -53,8 +57,22 @@ public class ShareWithActivity extends XmppActivity { } @Override - public void success(Message message) { + public void success(final Message message) { xmppConnectionService.sendMessage(message); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (mToast != null) { + mToast.cancel(); + } + if (share.uuid != null) { + mToast = Toast.makeText(getApplicationContext(), + getString(share.image ? R.string.shared_image_with_x : R.string.shared_file_with_x,message.getConversation().getName()), + Toast.LENGTH_SHORT); + mToast.show(); + } + } + }); } @Override @@ -69,7 +87,7 @@ public class ShareWithActivity extends XmppActivity { if (requestCode == REQUEST_START_NEW_CONVERSATION && resultCode == RESULT_OK) { share.contact = data.getStringExtra("contact"); - share.account = data.getStringExtra("account"); + share.account = data.getStringExtra(EXTRA_ACCOUNT); } if (xmppConnectionServiceBound && share != null @@ -131,9 +149,12 @@ public class ShareWithActivity extends XmppActivity { return; } final String type = intent.getType(); + Log.d(Config.LOGTAG, "action: "+intent.getAction()+ ", type:"+type); + share.uuid = intent.getStringExtra("uuid"); if (Intent.ACTION_SEND.equals(intent.getAction())) { final Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM); if (type != null && uri != null && !type.equalsIgnoreCase("text/plain")) { + this.share.uris.clear(); this.share.uris.add(uri); this.share.image = type.startsWith("image/") || isImage(uri); } else { @@ -148,7 +169,11 @@ public class ShareWithActivity extends XmppActivity { this.share.uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); } if (xmppConnectionServiceBound) { - xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0); + if (share.uuid != null) { + share(); + } else { + xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0); + } } } @@ -165,7 +190,7 @@ public class ShareWithActivity extends XmppActivity { @Override void onBackendConnected() { if (xmppConnectionServiceBound && share != null - && share.contact != null && share.account != null) { + && ((share.contact != null && share.account != null) || share.uuid != null)) { share(); return; } @@ -174,26 +199,43 @@ public class ShareWithActivity extends XmppActivity { } private void share() { - Account account; - try { - account = xmppConnectionService.findAccountByJid(Jid.fromString(share.account)); - } catch (final InvalidJidException e) { - account = null; - } - if (account == null) { - return; - } final Conversation conversation; - try { - conversation = xmppConnectionService - .findOrCreateConversation(account, Jid.fromString(share.contact), false); - } catch (final InvalidJidException e) { - return; + if (share.uuid != null) { + conversation = xmppConnectionService.findConversationByUuid(share.uuid); + if (conversation == null) { + return; + } + }else{ + Account account; + try { + account = xmppConnectionService.findAccountByJid(Jid.fromString(share.account)); + } catch (final InvalidJidException e) { + account = null; + } + if (account == null) { + return; + } + + try { + conversation = xmppConnectionService + .findOrCreateConversation(account, Jid.fromString(share.contact), false); + } catch (final InvalidJidException e) { + return; + } } share(conversation); } private void share(final Conversation conversation) { + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP && !hasPgp()) { + if (share.uuid == null) { + showInstallPgpDialog(); + } else { + Toast.makeText(this,R.string.openkeychain_not_installed,Toast.LENGTH_SHORT).show(); + finish(); + } + return; + } if (share.uris.size() != 0) { OnPresenceSelected callback; if (this.share.image) { @@ -209,22 +251,25 @@ public class ShareWithActivity extends XmppActivity { callback = new OnPresenceSelected() { @Override public void onPresenceSelected() { - Toast.makeText(getApplicationContext(), + mToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_file), - Toast.LENGTH_LONG).show(); + Toast.LENGTH_LONG); + mToast.show(); ShareWithActivity.this.xmppConnectionService .attachFileToConversation(conversation, share.uris.get(0), attachFileCallback); - switchToConversation(conversation, null, true); + if (share.uuid == null) { + switchToConversation(conversation, null, true); + } finish(); } }; } if (conversation.getAccount().httpUploadAvailable()) { - callback.onPresenceSelected(); + callback.onPresenceSelected(); } else { - selectPresence(conversation, callback); + selectPresence(conversation, callback); } } else { switchToConversation(conversation, this.share.text, true); @@ -233,4 +278,8 @@ public class ShareWithActivity extends XmppActivity { } + public void refreshUiReal() { + //nothing to do. This Activity doesn't implement any listeners + } + } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/StartConversationActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/StartConversationActivity.java index 8ad23e6e..f8c624be 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/StartConversationActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/StartConversationActivity.java @@ -1,5 +1,6 @@ package de.thedevstack.conversationsplus.ui; +import android.Manifest; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.ActionBar; @@ -13,6 +14,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefRecord; @@ -41,6 +43,7 @@ import android.widget.Checkable; import android.widget.EditText; import android.widget.ListView; import android.widget.Spinner; +import android.widget.TextView; import android.widget.Toast; import com.google.zxing.integration.android.IntentIntegrator; @@ -50,10 +53,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Blockable; @@ -61,13 +65,13 @@ import de.thedevstack.conversationsplus.entities.Bookmark; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.ListItem; -import de.thedevstack.conversationsplus.entities.Presences; +import de.thedevstack.conversationsplus.entities.Presence; import de.thedevstack.conversationsplus.services.XmppConnectionService.OnRosterUpdate; import de.thedevstack.conversationsplus.ui.adapter.KnownHostsAdapter; import de.thedevstack.conversationsplus.ui.adapter.ListItemAdapter; import de.thedevstack.conversationsplus.utils.XmppUri; -import de.thedevstack.conversationsplus.xmpp.XmppConnection; import de.thedevstack.conversationsplus.xmpp.OnUpdateBlocklist; +import de.thedevstack.conversationsplus.xmpp.XmppConnection; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; @@ -90,6 +94,9 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU private Invite mPendingInvite = null; private Menu mOptionsMenu; private EditText mSearchEditText; + private AtomicBoolean mRequestedContactsPermission = new AtomicBoolean(false); + private final int REQUEST_SYNC_CONTACTS = 0x3b28cf; + private MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() { @Override @@ -246,6 +253,12 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } + @Override + public void onStart() { + super.onStart(); + askForContactsPermissions(); + } + protected void openConversationForContact(int position) { Contact contact = (Contact) contacts.get(position); Conversation conversation = xmppConnectionService @@ -275,7 +288,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU if (!conversation.getMucOptions().online()) { xmppConnectionService.joinMuc(conversation); } - if (!bookmark.autojoin()) { + if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", true)) { bookmark.setAutojoin(true); xmppConnectionService.pushBookmarks(bookmark.getAccount()); } @@ -290,7 +303,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU protected void toggleContactBlock() { final int position = contact_context_id; - BlockContactDialog.show(this, xmppConnectionService, (Contact)contacts.get(position)); + BlockContactDialog.show(this, xmppConnectionService, (Contact) contacts.get(position)); } protected void deleteContact() { @@ -300,7 +313,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.action_delete_contact); builder.setMessage(getString(R.string.remove_contact_text, - contact.getJid())); + contact.getJid())); builder.setPositiveButton(R.string.delete, new OnClickListener() { @Override @@ -320,7 +333,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.delete_bookmark); builder.setMessage(getString(R.string.remove_bookmark_text, - bookmark.getJid())); + bookmark.getJid())); builder.setPositiveButton(R.string.delete, new OnClickListener() { @Override @@ -338,66 +351,37 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU @SuppressLint("InflateParams") protected void showCreateContactDialog(final String prefilledJid, final String fingerprint) { - 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); - if (fingerprint!=null) { - jid.setFocusable(false); - jid.setFocusableInTouchMode(false); - jid.setClickable(false); - jid.setCursorVisible(false); - } - } - 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() { + EnterJidDialog dialog = new EnterJidDialog( + this, mKnownHosts, mActivatedAccounts, + getString(R.string.create_contact), getString(R.string.create), + prefilledJid, null, fingerprint == null + ); - @Override - public void onClick(final View v) { - if (!xmppConnectionServiceBound) { - return; - } - final Jid accountJid; - try { - accountJid = Jid.fromString((String) spinner.getSelectedItem()); - } catch (final InvalidJidException e) { - return; - } - final Jid contactJid; - try { - contactJid = Jid.fromString(jid.getText().toString()); - } catch (final InvalidJidException e) { - jid.setError(getString(R.string.invalid_jid)); - return; - } - final Account account = xmppConnectionService - .findAccountByJid(accountJid); - if (account == null) { - dialog.dismiss(); - return; - } - final Contact contact = account.getRoster().getContact(contactJid); - if (contact.showInRoster()) { - jid.setError(getString(R.string.contact_already_exists)); - } else { - contact.addOtrFingerprint(fingerprint); - xmppConnectionService.createContact(contact); - dialog.dismiss(); - switchToConversation(contact); - } - } - }); + dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() { + @Override + public boolean onEnterJidDialogPositive(Jid accountJid, Jid contactJid) throws EnterJidDialog.JidError { + if (!xmppConnectionServiceBound) { + return false; + } + + final Account account = xmppConnectionService.findAccountByJid(accountJid); + if (account == null) { + return true; + } + final Contact contact = account.getRoster().getContact(contactJid); + if (contact.showInRoster()) { + throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists)); + } else { + contact.addOtrFingerprint(fingerprint); + xmppConnectionService.createContact(contact); + switchToConversation(contact); + return true; + } + } + }); + + dialog.show(); } @SuppressLint("InflateParams") @@ -407,11 +391,17 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU final 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)); + final boolean lock = Config.LOCK_DOMAINS_IN_CONVERSATIONS && Config.CONFERENCE_DOMAIN_LOCK != null; + final TextView jabberIdDesc = (TextView) dialogView.findViewById(R.id.jabber_id); + jabberIdDesc.setText(lock ? R.string.conference_name : R.string.conference_address); + jid.setHint(lock ? R.string.conference_name : R.string.conference_address_example); + if (!lock) { + jid.setAdapter(new KnownHostsAdapter(this, android.R.layout.simple_list_item_1, mKnownConferenceHosts)); + } if (prefilledJid != null) { jid.append(prefilledJid); } - populateAccountSpinner(spinner); + populateAccountSpinner(this, mActivatedAccounts, spinner); final Checkable bookmarkCheckBox = (CheckBox) dialogView .findViewById(R.id.bookmark); builder.setView(dialogView); @@ -427,49 +417,48 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU if (!xmppConnectionServiceBound) { return; } - final Jid accountJid; - try { - accountJid = Jid.fromString((String) spinner.getSelectedItem()); - } catch (final InvalidJidException e) { + final Account account = getSelectedAccount(spinner); + if (account == null) { return; } final Jid conferenceJid; try { - conferenceJid = Jid.fromString(jid.getText().toString()); + if (lock) { + conferenceJid = Jid.fromParts(jid.getText().toString(),Config.CONFERENCE_DOMAIN_LOCK, null); + } else { + conferenceJid = Jid.fromString(jid.getText().toString()); + } } catch (final InvalidJidException e) { - jid.setError(getString(R.string.invalid_jid)); - return; - } - final Account account = xmppConnectionService - .findAccountByJid(accountJid); - if (account == null) { - dialog.dismiss(); + jid.setError(getString(lock ? R.string.invalid_conference_name : R.string.invalid_jid)); return; } + if (bookmarkCheckBox.isChecked()) { if (account.hasBookmarkFor(conferenceJid)) { jid.setError(getString(R.string.bookmark_already_exists)); } else { - final Bookmark bookmark = new Bookmark(account,conferenceJid.toBareJid()); - bookmark.setAutojoin(true); + final Bookmark bookmark = new Bookmark(account, conferenceJid.toBareJid()); + bookmark.setAutojoin(getPreferences().getBoolean("autojoin", true)); + String nick = conferenceJid.getResourcepart(); + if (nick != null && !nick.isEmpty()) { + bookmark.setNick(nick); + } account.getBookmarks().add(bookmark); - xmppConnectionService - .pushBookmarks(account); + xmppConnectionService.pushBookmarks(account); final Conversation conversation = xmppConnectionService - .findOrCreateConversation(account, - conferenceJid, true); + .findOrCreateConversation(account, + conferenceJid, true); conversation.setBookmark(bookmark); if (!conversation.getMucOptions().online()) { - xmppConnectionService - .joinMuc(conversation); + xmppConnectionService.joinMuc(conversation); } dialog.dismiss(); switchToConversation(conversation); } } else { final Conversation conversation = xmppConnectionService - .findOrCreateConversation(account, - conferenceJid, true); + .findOrCreateConversation(account, + conferenceJid, true); if (!conversation.getMucOptions().online()) { xmppConnectionService.joinMuc(conversation); } @@ -480,6 +469,23 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU }); } + private Account getSelectedAccount(Spinner spinner) { + if (!spinner.isEnabled()) { + return null; + } + Jid jid; + try { + if (Config.DOMAIN_LOCK != null) { + jid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null); + } else { + jid = Jid.fromString((String) spinner.getSelectedItem()); + } + } catch (final InvalidJidException e) { + return null; + } + return xmppConnectionService.findAccountByJid(jid); + } + protected void switchToConversation(Contact contact) { Conversation conversation = xmppConnectionService .findOrCreateConversation(contact.getAccount(), @@ -487,11 +493,21 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU switchToConversation(conversation); } - private void populateAccountSpinner(Spinner spinner) { - ArrayAdapter<String> adapter = new ArrayAdapter<>(this, - android.R.layout.simple_spinner_item, mActivatedAccounts); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinner.setAdapter(adapter); + public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) { + if (accounts.size() > 0) { + ArrayAdapter<String> adapter = new ArrayAdapter<>(context, + android.R.layout.simple_spinner_item, accounts); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + spinner.setEnabled(true); + } else { + ArrayAdapter<String> adapter = new ArrayAdapter<>(context, + android.R.layout.simple_spinner_item, + Arrays.asList(new String[]{context.getString(R.string.no_accounts)})); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + spinner.setEnabled(false); + } } @Override @@ -573,12 +589,51 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU super.onActivityResult(requestCode, requestCode, intent); } + private void askForContactsPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + if (mRequestedContactsPermission.compareAndSet(false, true)) { + if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.sync_with_contacts); + builder.setMessage(R.string.sync_with_contacts_long); + builder.setPositiveButton(R.string.next, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS); + } + } + }); + builder.create().show(); + } else { + requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 0); + } + } + } + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + if (grantResults.length > 0) + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) { + xmppConnectionService.loadPhoneContacts(); + } + } + } + @Override protected void onBackendConnected() { this.mActivatedAccounts.clear(); for (Account account : xmppConnectionService.getAccounts()) { if (account.getStatus() != Account.State.DISABLED) { - this.mActivatedAccounts.add(account.getJid().toBareJid().toString()); + if (Config.DOMAIN_LOCK != null) { + this.mActivatedAccounts.add(account.getJid().getLocalpart()); + } else { + this.mActivatedAccounts.add(account.getJid().toBareJid().toString()); + } } } final Intent intent = getIntent(); @@ -683,9 +738,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU for (Account account : xmppConnectionService.getAccounts()) { if (account.getStatus() != Account.State.DISABLED) { for (Contact contact : account.getRoster().getContacts()) { + Presence p = contact.getPresences().getMostAvailablePresence(); + Presence.Status s = p == null ? Presence.Status.OFFLINE : p.getStatus(); if (contact.showInRoster() && contact.match(needle) && (!this.mHideOfflineContacts - || contact.getPresences().getMostAvailableStatus() < Presences.OFFLINE)) { + || s.compareTo(Presence.Status.OFFLINE) < 0)) { this.contacts.add(contact); } } @@ -761,7 +818,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; if (mResContextMenu == R.menu.conference_context) { activity.conference_context_id = acmi.position; - } else { + } else if (mResContextMenu == R.menu.contact_context){ activity.contact_context_id = acmi.position; final Blockable contact = (Contact) activity.contacts.get(acmi.position); final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock); diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/TrustKeysActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/TrustKeysActivity.java new file mode 100644 index 00000000..167b207f --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/ui/TrustKeysActivity.java @@ -0,0 +1,301 @@ +package de.thedevstack.conversationsplus.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import org.whispersystems.libaxolotl.IdentityKey; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; +import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Contact; +import de.thedevstack.conversationsplus.xmpp.OnKeyStatusUpdated; +import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; + +public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated { + private Jid accountJid; + private Jid contactJid; + + private Contact contact; + private Account mAccount; + private TextView keyErrorMessage; + private LinearLayout keyErrorMessageCard; + private TextView ownKeysTitle; + private LinearLayout ownKeys; + private LinearLayout ownKeysCard; + private TextView foreignKeysTitle; + private LinearLayout foreignKeys; + private LinearLayout foreignKeysCard; + private Button mSaveButton; + private Button mCancelButton; + + private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS; + + private final Map<String, Boolean> ownKeysToTrust = new HashMap<>(); + private final Map<String, Boolean> foreignKeysToTrust = new HashMap<>(); + + private final OnClickListener mSaveButtonListener = new OnClickListener() { + @Override + public void onClick(View v) { + commitTrusts(); + finishOk(); + } + }; + + private final OnClickListener mCancelButtonListener = new OnClickListener() { + @Override + public void onClick(View v) { + setResult(RESULT_CANCELED); + finish(); + } + }; + + @Override + protected void refreshUiReal() { + invalidateOptionsMenu(); + populateView(); + } + + @Override + protected String getShareableUri() { + if (contact != null) { + return contact.getShareableUri(); + } else { + return ""; + } + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_trust_keys); + try { + this.accountJid = Jid.fromString(getIntent().getExtras().getString(EXTRA_ACCOUNT)); + } catch (final InvalidJidException ignored) { + } + try { + this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact")); + } catch (final InvalidJidException ignored) { + } + + keyErrorMessageCard = (LinearLayout) findViewById(R.id.key_error_message_card); + keyErrorMessage = (TextView) findViewById(R.id.key_error_message); + ownKeysTitle = (TextView) findViewById(R.id.own_keys_title); + ownKeys = (LinearLayout) findViewById(R.id.own_keys_details); + ownKeysCard = (LinearLayout) findViewById(R.id.own_keys_card); + foreignKeysTitle = (TextView) findViewById(R.id.foreign_keys_title); + foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys_details); + foreignKeysCard = (LinearLayout) findViewById(R.id.foreign_keys_card); + mCancelButton = (Button) findViewById(R.id.cancel_button); + mCancelButton.setOnClickListener(mCancelButtonListener); + mSaveButton = (Button) findViewById(R.id.save_button); + mSaveButton.setOnClickListener(mSaveButtonListener); + + + if (getActionBar() != null) { + getActionBar().setHomeButtonEnabled(true); + getActionBar().setDisplayHomeAsUpEnabled(true); + } + } + + private void populateView() { + setTitle(getString(R.string.trust_omemo_fingerprints)); + ownKeys.removeAllViews(); + foreignKeys.removeAllViews(); + boolean hasOwnKeys = false; + boolean hasForeignKeys = false; + for(final String fingerprint : ownKeysToTrust.keySet()) { + hasOwnKeys = true; + addFingerprintRowWithListeners(ownKeys, contact.getAccount(), fingerprint, false, + XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)), false, + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + ownKeysToTrust.put(fingerprint, isChecked); + // own fingerprints have no impact on locked status. + } + }, + null, + null + ); + } + for(final String fingerprint : foreignKeysToTrust.keySet()) { + hasForeignKeys = true; + addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), fingerprint, false, + XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint)), false, + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + foreignKeysToTrust.put(fingerprint, isChecked); + lockOrUnlockAsNeeded(); + } + }, + null, + null + ); + } + + if(hasOwnKeys) { + ownKeysTitle.setText(accountJid.toString()); + ownKeysCard.setVisibility(View.VISIBLE); + } + if(hasForeignKeys) { + foreignKeysTitle.setText(contactJid.toString()); + foreignKeysCard.setVisibility(View.VISIBLE); + } + if(hasPendingKeyFetches()) { + setFetching(); + lock(); + } else { + if (!hasForeignKeys && hasNoOtherTrustedKeys()) { + keyErrorMessageCard.setVisibility(View.VISIBLE); + if (lastFetchReport == AxolotlService.FetchStatus.ERROR + || contact.getAccount().getAxolotlService().fetchMapHasErrors(contact)) { + keyErrorMessage.setText(R.string.error_no_keys_to_trust_server_error); + } else { + keyErrorMessage.setText(R.string.error_no_keys_to_trust); + } + ownKeys.removeAllViews(); ownKeysCard.setVisibility(View.GONE); + foreignKeys.removeAllViews(); foreignKeysCard.setVisibility(View.GONE); + } + lockOrUnlockAsNeeded(); + setDone(); + } + } + + private boolean reloadFingerprints() { + ownKeysToTrust.clear(); + foreignKeysToTrust.clear(); + AxolotlService service = this.mAccount.getAxolotlService(); + Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED); + Set<IdentityKey> foreignKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, contact); + if (hasNoOtherTrustedKeys() && ownKeysSet.size() == 0) { + foreignKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact)); + } + for(final IdentityKey identityKey : ownKeysSet) { + if(!ownKeysToTrust.containsKey(identityKey)) { + ownKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false); + } + } + for(final IdentityKey identityKey : foreignKeysSet) { + if(!foreignKeysToTrust.containsKey(identityKey)) { + foreignKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false); + } + } + return ownKeysSet.size() + foreignKeysSet.size() > 0; + } + + @Override + public void onBackendConnected() { + if ((accountJid != null) && (contactJid != null)) { + this.mAccount = xmppConnectionService.findAccountByJid(accountJid); + if (this.mAccount == null) { + return; + } + this.contact = this.mAccount.getRoster().getContact(contactJid); + reloadFingerprints(); + populateView(); + } + } + + private boolean hasNoOtherTrustedKeys() { + return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0; + } + + private boolean hasPendingKeyFetches() { + return mAccount != null && contact != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount,contact); + } + + + @Override + public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) { + if (report != null) { + lastFetchReport = report; + runOnUiThread(new Runnable() { + @Override + public void run() { + switch (report) { + case ERROR: + Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show(); + break; + case SUCCESS_VERIFIED: + Toast.makeText(TrustKeysActivity.this,R.string.verified_omemo_key_with_certificate,Toast.LENGTH_LONG).show(); + break; + } + } + }); + + } + boolean keysToTrust = reloadFingerprints(); + if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { + refreshUi(); + } else { + runOnUiThread(new Runnable() { + @Override + public void run() { + finishOk(); + } + }); + + } + } + + private void finishOk() { + Intent data = new Intent(); + data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID)); + setResult(RESULT_OK, data); + finish(); + } + + private void commitTrusts() { + for(final String fingerprint :ownKeysToTrust.keySet()) { + contact.getAccount().getAxolotlService().setFingerprintTrust( + fingerprint, + XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint))); + } + for(final String fingerprint:foreignKeysToTrust.keySet()) { + contact.getAccount().getAxolotlService().setFingerprintTrust( + fingerprint, + XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint))); + } + } + + private void unlock() { + mSaveButton.setEnabled(true); + mSaveButton.setTextColor(getPrimaryTextColor()); + } + + private void lock() { + mSaveButton.setEnabled(false); + mSaveButton.setTextColor(getSecondaryTextColor()); + } + + private void lockOrUnlockAsNeeded() { + if (hasNoOtherTrustedKeys() && !foreignKeysToTrust.values().contains(true)){ + lock(); + } else { + unlock(); + } + } + + private void setDone() { + mSaveButton.setText(getString(R.string.done)); + } + + private void setFetching() { + mSaveButton.setText(getString(R.string.fetching_keys)); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/UiCallback.java b/src/main/java/de/thedevstack/conversationsplus/ui/UiCallback.java index 0d23d29e..d5291b80 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/UiCallback.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/UiCallback.java @@ -3,9 +3,9 @@ package de.thedevstack.conversationsplus.ui; import android.app.PendingIntent; public interface UiCallback<T> { - public void success(T object); + void success(T object); - public void error(int errorCode, T object); + void error(int errorCode, T object); - public void userInputRequried(PendingIntent pi, T object); + void userInputRequried(PendingIntent pi, T object); } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/VerifyOTRActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/VerifyOTRActivity.java index 9f867dd2..494d4bea 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/VerifyOTRActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/VerifyOTRActivity.java @@ -196,9 +196,8 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer protected boolean handleIntent(Intent intent) { if (intent != null && intent.getAction().equals(ACTION_VERIFY_CONTACT)) { - try { - this.mAccount = this.xmppConnectionService.findAccountByJid(Jid.fromString(intent.getExtras().getString("account"))); - } catch (final InvalidJidException ignored) { + this.mAccount = extractAccount(intent); + if (this.mAccount == null) { return false; } try { diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/XmppActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/XmppActivity.java index 40fbe7c1..1fbb3b1d 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/XmppActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/XmppActivity.java @@ -1,5 +1,6 @@ package de.thedevstack.conversationsplus.ui; +import android.Manifest; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.ActionBar; @@ -16,6 +17,7 @@ import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentSender.SendIntentException; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; @@ -34,15 +36,19 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.PowerManager; import android.os.SystemClock; +import android.preference.PreferenceManager; import android.text.InputType; import android.util.DisplayMetrics; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; +import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.TextView; import android.widget.Toast; import com.google.zxing.BarcodeFormat; @@ -62,9 +68,11 @@ import java.util.List; import java.util.concurrent.RejectedExecutionException; import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.utils.ImageUtil; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.Conversation; @@ -73,8 +81,10 @@ import de.thedevstack.conversationsplus.entities.MucOptions; import de.thedevstack.conversationsplus.entities.Presences; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.services.XmppConnectionService.XmppConnectionBinder; +import de.thedevstack.conversationsplus.ui.widget.Switch; +import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.ExceptionHelper; -import de.thedevstack.conversationsplus.utils.ImageUtil; +import de.thedevstack.conversationsplus.xmpp.OnKeyStatusUpdated; import de.thedevstack.conversationsplus.xmpp.OnUpdateBlocklist; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; @@ -83,6 +93,10 @@ public abstract class XmppActivity extends Activity { protected static final int REQUEST_ANNOUNCE_PGP = 0x0101; protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102; + protected static final int REQUEST_CHOOSE_PGP_ID = 0x0103; + protected static final int REQUEST_BATTERY_OP = 0x13849ff; + + public static final String EXTRA_ACCOUNT = "account"; public XmppConnectionService xmppConnectionService; public boolean xmppConnectionServiceBound = false; @@ -90,6 +104,7 @@ public abstract class XmppActivity extends Activity { protected int mPrimaryTextColor; protected int mSecondaryTextColor; + protected int mTertiaryTextColor; protected int mPrimaryBackgroundColor; protected int mSecondaryBackgroundColor; protected int mColorRed; @@ -97,6 +112,8 @@ public abstract class XmppActivity extends Activity { protected int mColorGreen; protected int mPrimaryColor; + protected boolean mUseSubject = true; + private DisplayMetrics metrics; protected int mTheme; protected boolean mUsingEnterKey = false; @@ -114,7 +131,7 @@ public abstract class XmppActivity extends Activity { protected ConferenceInvite mPendingConferenceInvite = null; - protected void refreshUi() { + protected final void refreshUi() { final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh; if (diff > Config.REFRESH_UI_INTERVAL) { mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable); @@ -126,9 +143,7 @@ public abstract class XmppActivity extends Activity { } } - protected void refreshUiReal() { - - }; + abstract protected void refreshUiReal(); protected interface OnValueEdited { public void onValueEdited(String value); @@ -273,6 +288,9 @@ public abstract class XmppActivity extends Activity { if (this instanceof XmppConnectionService.OnAccountUpdate) { this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this); } + if (this instanceof XmppConnectionService.OnCaptchaRequested) { + this.xmppConnectionService.setOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this); + } if (this instanceof XmppConnectionService.OnRosterUpdate) { this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this); } @@ -285,6 +303,9 @@ public abstract class XmppActivity extends Activity { if (this instanceof XmppConnectionService.OnShowErrorToast) { this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this); } + if (this instanceof OnKeyStatusUpdated) { + this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this); + } } protected void unregisterListeners() { @@ -294,6 +315,9 @@ public abstract class XmppActivity extends Activity { if (this instanceof XmppConnectionService.OnAccountUpdate) { this.xmppConnectionService.removeOnAccountListChangedListener(); } + if (this instanceof XmppConnectionService.OnCaptchaRequested) { + this.xmppConnectionService.removeOnCaptchaRequestedListener(); + } if (this instanceof XmppConnectionService.OnRosterUpdate) { this.xmppConnectionService.removeOnRosterUpdateListener(); } @@ -306,6 +330,9 @@ public abstract class XmppActivity extends Activity { if (this instanceof XmppConnectionService.OnShowErrorToast) { this.xmppConnectionService.removeOnShowErrorToastListener(); } + if (this instanceof OnKeyStatusUpdated) { + this.xmppConnectionService.removeOnNewKeysAvailableListener(); + } } @Override @@ -334,34 +361,63 @@ public abstract class XmppActivity extends Activity { ExceptionHelper.init(getApplicationContext()); mPrimaryTextColor = getResources().getColor(R.color.primaryText); mSecondaryTextColor = getResources().getColor(R.color.secondaryText); + mTertiaryTextColor = getResources().getColor(R.color.black12); mColorRed = getResources().getColor(R.color.warning); + mColorOrange = getResources().getColor(R.color.orange500); mColorGreen = getResources().getColor(R.color.online); + mPrimaryColor = getResources().getColor(R.color.primary); mPrimaryBackgroundColor = getResources().getColor(R.color.primaryBackground); mSecondaryBackgroundColor = getResources().getColor(R.color.secondaryBackground); this.mTheme = findTheme(); setTheme(this.mTheme); this.mUsingEnterKey = ConversationsPlusPreferences.displayEnterKey(); - + mUseSubject = getPreferences().getBoolean("use_subject", true); final ActionBar ab = getActionBar(); if (ab!=null) { ab.setDisplayHomeAsUpEnabled(true); } } + protected boolean showBatteryOptimizationWarning() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + return !pm.isIgnoringBatteryOptimizations(getPackageName()); + } else { + return false; + } + } + + protected boolean usingEnterKey() { + return getPreferences().getBoolean("display_enter_key", false); + } + + 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) { - switchToConversation(conversation,text,null,newTask); + switchToConversation(conversation,text,null,false,newTask); } public void highlightInMuc(Conversation conversation, String nick) { - switchToConversation(conversation, null, nick, false); + switchToConversation(conversation, null, nick, false, false); } - private void switchToConversation(Conversation conversation, String text, String nick, boolean newTask) { + public void privateMsgInMuc(Conversation conversation, String nick) { + switchToConversation(conversation, null, nick, true, false); + } + + private void switchToConversation(Conversation conversation, String text, String nick, boolean pm, boolean newTask) { Intent viewConversationIntent = new Intent(this, ConversationActivity.class); viewConversationIntent.setAction(Intent.ACTION_VIEW); @@ -372,6 +428,7 @@ public abstract class XmppActivity extends Activity { } if (nick != null) { viewConversationIntent.putExtra(ConversationActivity.NICK, nick); + viewConversationIntent.putExtra(ConversationActivity.PRIVATE_MESSAGE,pm); } viewConversationIntent.setType(ConversationActivity.VIEW_CONVERSATION); if (newTask) { @@ -387,16 +444,26 @@ public abstract class XmppActivity extends Activity { } public void switchToContactDetails(Contact contact) { + switchToContactDetails(contact, null); + } + + public void switchToContactDetails(Contact contact, String messageFingerprint) { Intent intent = new Intent(this, ContactDetailsActivity.class); intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); - intent.putExtra("account", contact.getAccount().getJid().toBareJid().toString()); + intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().toBareJid().toString()); intent.putExtra("contact", contact.getJid().toString()); + intent.putExtra("fingerprint", messageFingerprint); startActivity(intent); } public void switchToAccount(Account account) { + switchToAccount(account, false); + } + + public void switchToAccount(Account account, boolean init) { Intent intent = new Intent(this, EditAccountActivity.class); intent.putExtra("jid", account.getJid().toBareJid().toString()); + intent.putExtra("init", init); startActivity(intent); } @@ -417,54 +484,81 @@ public abstract class XmppActivity extends Activity { intent.putExtra("filter_contacts", contacts.toArray(new String[contacts.size()])); intent.putExtra("conversation", conversation.getUuid()); intent.putExtra("multiple", true); + intent.putExtra("show_enter_jid", true); + intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); 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 (final SendIntentException ignored) { - } - } + if (account.getPgpId() == -1) { + choosePgpSignId(account); + } else { + xmppConnectionService.getPgpEngine().generateSignature(account, "", new UiCallback<Account>() { - @Override - public void success(Account account) { - xmppConnectionService.databaseBackend.updateAccount(account); - xmppConnectionService.sendPresence(account); - if (conversation != null) { - conversation.setNextEncryption(Message.ENCRYPTION_PGP); - xmppConnectionService.databaseBackend.updateConversation(conversation); - } - } + @Override + public void userInputRequried(PendingIntent pi, + Account account) { + try { + startIntentSenderForResult(pi.getIntentSender(), + REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); + } catch (final SendIntentException ignored) { + } + } - @Override - public void error(int error, Account account) { - displayErrorDialog(error); - } - }); + @Override + public void success(Account account) { + xmppConnectionService.databaseBackend.updateAccount(account); + xmppConnectionService.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 choosePgpSignId(Account account) { + xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback<Account>() { + @Override + public void success(Account account1) { + } + + @Override + public void error(int errorCode, Account object) { + + } + + @Override + public void userInputRequried(PendingIntent pi, Account object) { + try { + startIntentSenderForResult(pi.getIntentSender(), + REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0); + } catch (final SendIntentException ignored) { + } + } + }); } public 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(); - } - }); + @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(); + } + }); } @@ -503,9 +597,9 @@ public abstract class XmppActivity extends Activity { public void onClick(DialogInterface dialog, int which) { if (xmppConnectionServiceBound) { xmppConnectionService.sendPresencePacket(contact - .getAccount(), xmppConnectionService - .getPresenceGenerator() - .requestPresenceUpdatesFrom(contact)); + .getAccount(), xmppConnectionService + .getPresenceGenerator() + .requestPresenceUpdatesFrom(contact)); } } }); @@ -571,6 +665,151 @@ public abstract class XmppActivity extends Activity { builder.create().show(); } + protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight, View.OnClickListener onKeyClickedListener) { + final XmppAxolotlSession.Trust trust = account.getAxolotlService() + .getFingerprintTrust(fingerprint); + if (trust == null) { + return false; + } + return addFingerprintRowWithListeners(keys, account, fingerprint, highlight, trust, true, + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + account.getAxolotlService().setFingerprintTrust(fingerprint, + (isChecked) ? XmppAxolotlSession.Trust.TRUSTED : + XmppAxolotlSession.Trust.UNTRUSTED); + } + }, + new View.OnClickListener() { + @Override + public void onClick(View v) { + account.getAxolotlService().setFingerprintTrust(fingerprint, + XmppAxolotlSession.Trust.UNTRUSTED); + v.setEnabled(true); + } + }, + onKeyClickedListener + + ); + } + + protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account, + final String fingerprint, + boolean highlight, + XmppAxolotlSession.Trust trust, + boolean showTag, + CompoundButton.OnCheckedChangeListener + onCheckedChangeListener, + View.OnClickListener onClickListener, + View.OnClickListener onKeyClickedListener) { + if (trust == XmppAxolotlSession.Trust.COMPROMISED) { + return false; + } + View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false); + TextView key = (TextView) view.findViewById(R.id.key); + key.setOnClickListener(onKeyClickedListener); + TextView keyType = (TextView) view.findViewById(R.id.key_type); + keyType.setOnClickListener(onKeyClickedListener); + Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust); + trustToggle.setVisibility(View.VISIBLE); + trustToggle.setOnCheckedChangeListener(onCheckedChangeListener); + trustToggle.setOnClickListener(onClickListener); + final View.OnLongClickListener purge = new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + showPurgeKeyDialog(account, fingerprint); + return true; + } + }; + view.setOnLongClickListener(purge); + key.setOnLongClickListener(purge); + keyType.setOnLongClickListener(purge); + boolean x509 = trust == XmppAxolotlSession.Trust.TRUSTED_X509 || trust == XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509; + switch (trust) { + case UNTRUSTED: + case TRUSTED: + case TRUSTED_X509: + trustToggle.setChecked(trust.trusted(), false); + trustToggle.setEnabled(trust != XmppAxolotlSession.Trust.TRUSTED_X509); + if (trust == XmppAxolotlSession.Trust.TRUSTED_X509) { + trustToggle.setOnClickListener(null); + } + key.setTextColor(getPrimaryTextColor()); + keyType.setTextColor(getSecondaryTextColor()); + break; + case UNDECIDED: + trustToggle.setChecked(false, false); + trustToggle.setEnabled(false); + key.setTextColor(getPrimaryTextColor()); + keyType.setTextColor(getSecondaryTextColor()); + break; + case INACTIVE_UNTRUSTED: + case INACTIVE_UNDECIDED: + trustToggle.setOnClickListener(null); + trustToggle.setChecked(false, false); + trustToggle.setEnabled(false); + key.setTextColor(getTertiaryTextColor()); + keyType.setTextColor(getTertiaryTextColor()); + break; + case INACTIVE_TRUSTED: + case INACTIVE_TRUSTED_X509: + trustToggle.setOnClickListener(null); + trustToggle.setChecked(true, false); + trustToggle.setEnabled(false); + key.setTextColor(getTertiaryTextColor()); + keyType.setTextColor(getTertiaryTextColor()); + break; + } + + if (showTag) { + keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint)); + } else { + keyType.setVisibility(View.GONE); + } + if (highlight) { + keyType.setTextColor(getResources().getColor(R.color.accent)); + keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message)); + } else { + keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint)); + } + + key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2))); + keys.addView(view); + return true; + } + + public void showPurgeKeyDialog(final Account account, final String fingerprint) { + Builder builder = new Builder(this); + builder.setTitle(getString(R.string.purge_key)); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage(getString(R.string.purge_key_desc_part1) + + "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2)) + + "\n\n" + getString(R.string.purge_key_desc_part2)); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.accept), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + account.getAxolotlService().purgeKey(fingerprint); + refreshUi(); + } + }); + builder.create().show(); + } + + public boolean hasStoragePermission(int requestCode) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode); + return false; + } else { + return true; + } + } else { + return true; + } + } + public void selectPresence(final Conversation conversation, final OnPresenceSelected listener) { final Contact contact = conversation.getContact(); @@ -690,6 +929,10 @@ public abstract class XmppActivity extends Activity { } }; + public int getTertiaryTextColor() { + return this.mTertiaryTextColor; + } + public int getSecondaryTextColor() { return this.mSecondaryTextColor; } @@ -737,7 +980,7 @@ public abstract class XmppActivity extends Activity { @Override public NdefMessage createNdefMessage(NfcEvent nfcEvent) { return new NdefMessage(new NdefRecord[]{ - NdefRecord.createUri(getShareableUri()), + NdefRecord.createUri(getShareableUri()), NdefRecord.createApplicationRecord("de.thedevstack.conversationsplus") }); } @@ -818,6 +1061,15 @@ public abstract class XmppActivity extends Activity { } } + protected Account extractAccount(Intent intent) { + String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null; + try { + return jid != null ? xmppConnectionService.findAccountByJid(Jid.fromString(jid)) : null; + } catch (InvalidJidException e) { + return null; + } + } + public static class ConferenceInvite { private String uuid; private List<Jid> jids = new ArrayList<>(); diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/AccountAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/AccountAdapter.java index 6195f47b..eb0344bc 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/AccountAdapter.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/AccountAdapter.java @@ -1,12 +1,5 @@ package de.thedevstack.conversationsplus.ui.adapter; -import java.util.List; - -import de.thedevstack.conversationsplus.R; -import de.thedevstack.conversationsplus.entities.Account; -import de.thedevstack.conversationsplus.services.AvatarService; -import de.thedevstack.conversationsplus.ui.XmppActivity; -import de.thedevstack.conversationsplus.ui.ManageAccountActivity; import android.content.Context; import android.view.LayoutInflater; import android.view.View; @@ -15,7 +8,16 @@ import android.widget.ArrayAdapter; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.TextView; -import android.widget.Switch; + +import java.util.List; + +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.services.AvatarService; +import de.thedevstack.conversationsplus.ui.ManageAccountActivity; +import de.thedevstack.conversationsplus.ui.XmppActivity; +import de.thedevstack.conversationsplus.ui.widget.Switch; public class AccountAdapter extends ArrayAdapter<Account> { @@ -35,7 +37,11 @@ public class AccountAdapter extends ArrayAdapter<Account> { view = inflater.inflate(R.layout.account_row, parent, false); } TextView jid = (TextView) view.findViewById(R.id.account_jid); - jid.setText(account.getJid().toBareJid().toString()); + if (Config.DOMAIN_LOCK != null) { + jid.setText(account.getJid().getLocalpart()); + } else { + jid.setText(account.getJid().toBareJid().toString()); + } TextView statusView = (TextView) view.findViewById(R.id.account_status); ImageView imageView = (ImageView) view.findViewById(R.id.account_image); imageView.setImageBitmap(AvatarService.getInstance().get(account, activity.getPixel(48))); @@ -54,8 +60,7 @@ public class AccountAdapter extends ArrayAdapter<Account> { } final Switch tglAccountState = (Switch) view.findViewById(R.id.tgl_account_status); final boolean isDisabled = (account.getStatus() == Account.State.DISABLED); - tglAccountState.setOnCheckedChangeListener(null); - tglAccountState.setChecked(!isDisabled); + tglAccountState.setChecked(!isDisabled,false); tglAccountState.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) { diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java index 590a6cfd..ab131cd0 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java @@ -20,20 +20,17 @@ import java.lang.ref.WeakReference; import java.util.List; import java.util.concurrent.RejectedExecutionException; -import de.thedevstack.conversationsplus.ConversationsPlusPreferences; -import de.thedevstack.conversationsplus.services.AvatarService; import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener; import de.tzur.conversations.Settings; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; -import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.entities.Presences; +import de.thedevstack.conversationsplus.entities.Transferable; +import de.thedevstack.conversationsplus.services.AvatarService; import de.thedevstack.conversationsplus.ui.ConversationActivity; import de.thedevstack.conversationsplus.ui.XmppActivity; import de.thedevstack.conversationsplus.utils.UIHelper; -import github.ankushsachdeva.emojicon.EmojiconTextView; public class ConversationAdapter extends ArrayAdapter<Conversation> { @@ -60,14 +57,15 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { view.findViewById(R.id.conversationListRowFrame).setBackgroundColor(c); } TextView convName = (TextView) view.findViewById(R.id.conversation_name); - if (conversation.getMode() == Conversation.MODE_SINGLE || ConversationsPlusPreferences.useSubject()) { + if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) { convName.setText(conversation.getName()); } else { convName.setText(conversation.getJid().toBareJid().toString()); } - EmojiconTextView mLastMessage = (EmojiconTextView) view.findViewById(R.id.conversation_lastmsg); + 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); + ImageView notificationStatus = (ImageView) view.findViewById(R.id.notification_status); if (Settings.SHOW_ONLINE_STATUS && conversation.getAccount().getStatus() == Account.State.ONLINE) { TextView status = (TextView) view.findViewById(R.id.status); @@ -75,15 +73,15 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { String color = "#000000"; if (conversation.getMode() == Conversation.MODE_SINGLE) { switch (conversation.getContact().getMostAvailableStatus()) { - case Presences.ONLINE: - case Presences.CHAT: + case ONLINE: + case CHAT: color = "#259B23"; break; - case Presences.AWAY: - case Presences.XA: + case AWAY: + case XA: color = "#FF9800"; break; - case Presences.DND: + case DND: color = "#E51C23"; break; } @@ -113,7 +111,7 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { imagePreview.setVisibility(View.GONE); CharSequence msgText = preview.first; String msgPrefix = null; - if (message.getStatus() == Message.STATUS_SEND + if (message.getStatus() == Message.STATUS_SEND || message.getStatus() == Message.STATUS_SEND_DISPLAYED || message.getStatus() == Message.STATUS_SEND_FAILED || message.getStatus() == Message.STATUS_SEND_RECEIVED) { @@ -122,7 +120,7 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { msgPrefix = UIHelper.getMessageDisplayName(message); } String lastMessagePreview = ((null == msgPrefix || msgPrefix.isEmpty()) ? "" : (msgPrefix + ": ")) + msgText; - mLastMessage.setText(lastMessagePreview); + mLastMessage.setText(lastMessagePreview); if (preview.second) { if (conversation.isRead()) { mLastMessage.setTypeface(null, Typeface.ITALIC); @@ -138,6 +136,20 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { } } + long muted_till = conversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0); + if (muted_till == Long.MAX_VALUE) { + notificationStatus.setVisibility(View.VISIBLE); + notificationStatus.setImageResource(R.drawable.ic_notifications_off_grey600_24dp); + } else if (muted_till >= System.currentTimeMillis()) { + notificationStatus.setVisibility(View.VISIBLE); + notificationStatus.setImageResource(R.drawable.ic_notifications_paused_grey600_24dp); + } else if (conversation.alwaysNotify()) { + notificationStatus.setVisibility(View.GONE); + } else { + notificationStatus.setVisibility(View.VISIBLE); + notificationStatus.setImageResource(R.drawable.ic_notifications_none_grey600_24dp); + } + mTimestamp.setText(UIHelper.readableTimeDifference(activity, message.getTimeSent())); ImageView profilePicture = (ImageView) view.findViewById(R.id.conversation_image); profilePicture.setOnLongClickListener(new ShowResourcesListDialogListener(activity, conversation.getContact())); @@ -228,4 +240,4 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { return bitmapWorkerTaskReference.get(); } } -} +}
\ No newline at end of file diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/KnownHostsAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/KnownHostsAdapter.java index 7bca0aa6..41973089 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/KnownHostsAdapter.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/KnownHostsAdapter.java @@ -1,13 +1,13 @@ package de.thedevstack.conversationsplus.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; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + public class KnownHostsAdapter extends ArrayAdapter<String> { private ArrayList<String> domains; private Filter domainFilter = new Filter() { @@ -15,7 +15,7 @@ public class KnownHostsAdapter extends ArrayAdapter<String> { @Override protected FilterResults performFiltering(CharSequence constraint) { if (constraint != null) { - ArrayList<String> suggestions = new ArrayList<String>(); + ArrayList<String> suggestions = new ArrayList<>(); final String[] split = constraint.toString().split("@"); if (split.length == 1) { for (String domain : domains) { @@ -58,10 +58,9 @@ public class KnownHostsAdapter extends ArrayAdapter<String> { } }; - public KnownHostsAdapter(Context context, int viewResourceId, - List<String> mKnownHosts) { + public KnownHostsAdapter(Context context, int viewResourceId, List<String> mKnownHosts) { super(context, viewResourceId, new ArrayList<String>()); - domains = new ArrayList<String>(mKnownHosts); + domains = new ArrayList<>(mKnownHosts); } @Override diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ListItemAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ListItemAdapter.java index 4ecebd84..a67f5bcd 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ListItemAdapter.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ListItemAdapter.java @@ -1,18 +1,5 @@ package de.thedevstack.conversationsplus.ui.adapter; -import java.lang.ref.WeakReference; -import java.util.List; -import java.util.concurrent.RejectedExecutionException; - -import de.thedevstack.conversationsplus.ConversationsPlusPreferences; -import de.thedevstack.conversationsplus.services.AvatarService; -import de.tzur.conversations.Settings; -import de.thedevstack.conversationsplus.R; -import de.thedevstack.conversationsplus.entities.ListItem; -import de.thedevstack.conversationsplus.ui.XmppActivity; -import de.thedevstack.conversationsplus.utils.UIHelper; -import de.thedevstack.conversationsplus.xmpp.jid.Jid; - import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; @@ -27,6 +14,18 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.concurrent.RejectedExecutionException; + +import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.tzur.conversations.Settings; +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.entities.ListItem; +import de.thedevstack.conversationsplus.services.AvatarService; +import de.thedevstack.conversationsplus.ui.XmppActivity; +import de.thedevstack.conversationsplus.utils.UIHelper; + public class ListItemAdapter extends ArrayAdapter<ListItem> { protected XmppActivity activity; @@ -82,11 +81,12 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { tagLayout.addView(tv); } } - final Jid jid = item.getJid(); + final String jid = item.getDisplayJid(); if (jid != null) { - tvJid.setText(jid.toString()); + tvJid.setVisibility(View.VISIBLE); + tvJid.setText(jid); } else { - tvJid.setText(""); + tvJid.setVisibility(View.GONE); } tvName.setText(item.getDisplayName()); loadAvatar(item,picture); @@ -98,7 +98,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { } public interface OnTagClickedListener { - public void onTagClicked(String tag); + void onTagClicked(String tag); } class BitmapWorkerTask extends AsyncTask<ListItem, Void, Bitmap> { diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java index 647ef68d..6a141f11 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java @@ -1,16 +1,24 @@ package de.thedevstack.conversationsplus.ui.adapter; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.AsyncTask; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; +import android.util.DisplayMetrics; +import android.util.Patterns; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; @@ -22,32 +30,38 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import java.lang.ref.WeakReference; import java.util.List; +import java.util.concurrent.RejectedExecutionException; +import java.util.regex.Matcher; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; import de.thedevstack.conversationsplus.entities.Account; -import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.Conversation; -import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.Message.FileParams; +import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.services.AvatarService; import de.thedevstack.conversationsplus.ui.ConversationActivity; +import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.GeoHelper; import de.thedevstack.conversationsplus.utils.UIHelper; -import github.ankushsachdeva.emojicon.EmojiconTextView; 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; @@ -63,6 +77,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { public MessageAdapter(ConversationActivity activity, List<Message> messages) { super(activity, 0, messages); this.activity = activity; + metrics = getContext().getResources().getDisplayMetrics(); } public void setOnContactPictureClicked(OnContactPictureClicked listener) { @@ -79,24 +94,37 @@ public class MessageAdapter extends ArrayAdapter<Message> { return 3; } - @Override - public int getItemViewType(int position) { - if (getItem(position).getType() == Message.TYPE_STATUS) { + public int getItemViewType(Message message) { + if (message.getType() == Message.TYPE_STATUS) { return STATUS; - } else if (getItem(position).getStatus() <= Message.STATUS_RECEIVED) { + } else if (message.getStatus() <= Message.STATUS_RECEIVED) { return RECEIVED; + } + + return SENT; + } + + @Override + public int getItemViewType(int position) { + return this.getItemViewType(getItem(position)); + } + + private int getMessageTextColor(boolean onDark, boolean primary) { + if (onDark) { + return activity.getResources().getColor(primary ? R.color.white : R.color.white70); } else { - return SENT; + return activity.getResources().getColor(primary ? R.color.black87 : R.color.black54); } } - private void displayStatus(ViewHolder viewHolder, Message message) { + private void displayStatus(ViewHolder viewHolder, Message message, int type, boolean darkBackground) { 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.getType() == Message.TYPE_FILE || message.getTransferable() != null) { @@ -145,15 +173,40 @@ public class MessageAdapter extends ArrayAdapter<Message> { } break; } - if (error) { + if (error && type == SENT) { viewHolder.time.setTextColor(activity.getWarningTextColor()); } else { - viewHolder.time.setTextColor(activity.getSecondaryTextColor()); + viewHolder.time.setTextColor(this.getMessageTextColor(darkBackground,false)); } if (message.getEncryption() == Message.ENCRYPTION_NONE) { viewHolder.indicator.setVisibility(View.GONE); } else { + viewHolder.indicator.setImageResource(darkBackground ? R.drawable.ic_lock_white_18dp : R.drawable.ic_lock_black_18dp); viewHolder.indicator.setVisibility(View.VISIBLE); + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + XmppAxolotlSession.Trust trust = message.getConversation() + .getAccount().getAxolotlService().getFingerprintTrust( + message.getAxolotlFingerprint()); + + if(trust == null || (!trust.trusted() && !trust.trustedInactive())) { + viewHolder.indicator.setColorFilter(activity.getWarningTextColor()); + viewHolder.indicator.setAlpha(1.0f); + } else { + viewHolder.indicator.clearColorFilter(); + if (darkBackground) { + viewHolder.indicator.setAlpha(0.7f); + } else { + viewHolder.indicator.setAlpha(0.57f); + } + } + } else { + viewHolder.indicator.clearColorFilter(); + if (darkBackground) { + viewHolder.indicator.setAlpha(0.7f); + } else { + viewHolder.indicator.setAlpha(0.57f); + } + } } String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(), @@ -185,45 +238,32 @@ public class MessageAdapter extends ArrayAdapter<Message> { } } - private void displayInfoMessage(ViewHolder viewHolder, String text) { + private void displayInfoMessage(ViewHolder viewHolder, String text, boolean darkBackground) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(text); - viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor()); + viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false)); viewHolder.messageBody.setTypeface(null, Typeface.ITALIC); viewHolder.messageBody.setTextIsSelectable(false); } - private void displayDecryptionFailed(ViewHolder viewHolder) { + private void displayDecryptionFailed(ViewHolder viewHolder, boolean darkBackground) { 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()); + R.string.decryption_failed)); + viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setTextIsSelectable(false); } - private void displayHeartMessage(final ViewHolder viewHolder, final String body) { - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } - viewHolder.image.setVisibility(View.GONE); - viewHolder.messageBody.setVisibility(View.VISIBLE); - viewHolder.messageBody.setIncludeFontPadding(false); - Spannable span = new SpannableString(body); - span.setSpan(new RelativeSizeSpan(4.0f), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - span.setSpan(new ForegroundColorSpan(activity.getWarningTextColor()), 0, body.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - viewHolder.messageBody.setText(span); - } - - private void displayTextMessage(final ViewHolder viewHolder, final Message message) { + private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } @@ -232,7 +272,12 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.messageBody.setIncludeFontPadding(true); if (message.getBody() != null) { final String nick = UIHelper.getMessageDisplayName(message); - final String body = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND,nick + " "); + String body; + try { + body = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND, nick + " "); + } catch (ArrayIndexOutOfBoundsException e) { + body = message.getMergedBody(); + } final SpannableString formattedBody = new SpannableString(body); int i = body.indexOf(Message.MERGE_SEPARATOR); while(i >= 0) { @@ -241,14 +286,13 @@ public class MessageAdapter extends ArrayAdapter<Message> { i = body.indexOf(Message.MERGE_SEPARATOR,end); } if (message.getType() != Message.TYPE_PRIVATE) { - if (message.hasMeCommand()) { final Spannable span = new SpannableString(formattedBody); span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); viewHolder.messageBody.setText(span); } else { - viewHolder.messageBody.setText(formattedBody); + viewHolder.messageBody.setText(formattedBody); } } else { String privateMarker; @@ -266,8 +310,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { } final Spannable span = new SpannableString(privateMarker + " " + formattedBody); - span.setSpan(new ForegroundColorSpan(activity - .getSecondaryTextColor()), 0, privateMarker + span.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground,false)), 0, privateMarker .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); span.setSpan(new StyleSpan(Typeface.BOLD), 0, privateMarker.length(), @@ -279,12 +322,21 @@ public class MessageAdapter extends ArrayAdapter<Message> { } viewHolder.messageBody.setText(span); } + int urlCount = 0; + Matcher matcher = Patterns.WEB_URL.matcher(body); + while (matcher.find()) { + urlCount++; + } + viewHolder.messageBody.setTextIsSelectable(urlCount <= 1); } else { viewHolder.messageBody.setText(""); + viewHolder.messageBody.setTextIsSelectable(false); } - viewHolder.messageBody.setTextColor(activity.getPrimaryTextColor()); + viewHolder.messageBody.setTextColor(this.getMessageTextColor(darkBackground, true)); + viewHolder.messageBody.setLinkTextColor(this.getMessageTextColor(darkBackground, true)); + viewHolder.messageBody.setHighlightColor(activity.getResources().getColor(darkBackground ? R.color.grey800 : R.color.grey500)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); - viewHolder.messageBody.setTextIsSelectable(true); + viewHolder.messageBody.setOnLongClickListener(openContextMenu); } private void displayDownloadableMessage(ViewHolder viewHolder, @@ -295,11 +347,11 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.download_button.setText(text); viewHolder.download_button.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - startDownloadable(message); - } - }); + @Override + public void onClick(View v) { + activity.startDownloadable(message); + } + }); viewHolder.download_button.setOnLongClickListener(openContextMenu); } @@ -352,25 +404,25 @@ public class MessageAdapter extends ArrayAdapter<Message> { scalledW = (int) target; scalledH = (int) (params.height / ((double) params.width / target)); } - viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams( - scalledW, scalledH));*/ + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scalledW, scalledH); + layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4)); + viewHolder.image.setLayoutParams(layoutParams);*/ //TODO Why should this be calculated by hand??? activity.loadBitmap(message, viewHolder.image, true); - viewHolder.image.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(FileBackend.getJingleFileUri(message), "image/*"); - getContext().startActivity(intent); - } - }); + viewHolder.image.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + openDownloadable(message); + } + }); viewHolder.image.setOnLongClickListener(openContextMenu); } @Override public View getView(int position, View view, ViewGroup parent) { final Message message = getItem(position); + final boolean isInValidSession = message.isValidInSession(); final Conversation conversation = message.getConversation(); final Account account = conversation.getAccount(); final int type = getItemViewType(position); @@ -391,7 +443,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { .findViewById(R.id.security_indicator); viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (EmojiconTextView) view + viewHolder.messageBody = (TextView) view .findViewById(R.id.message_body); viewHolder.time = (TextView) view .findViewById(R.id.message_time); @@ -411,12 +463,13 @@ public class MessageAdapter extends ArrayAdapter<Message> { .findViewById(R.id.security_indicator); viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (EmojiconTextView) view + 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); + viewHolder.encryption = (TextView) view.findViewById(R.id.message_encryption); break; case STATUS: view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false); @@ -435,25 +488,20 @@ public class MessageAdapter extends ArrayAdapter<Message> { } } + boolean darkBackground = (type == RECEIVED && !isInValidSession); + if (type == STATUS) { + viewHolder.status_message.setVisibility(View.VISIBLE); + viewHolder.contact_picture.setVisibility(View.VISIBLE); if (conversation.getMode() == Conversation.MODE_SINGLE) { - viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get(conversation.getContact(), - activity.getPixel(32))); + viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get(conversation.getContact(), + activity.getPixel(32))); viewHolder.contact_picture.setAlpha(0.5f); - viewHolder.status_message.setText(message.getBody()); } + viewHolder.status_message.setText(message.getBody()); return view; - } else if (type == RECEIVED) { - Contact contact = message.getContact(); - if (contact != null) { - viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get(contact, activity.getPixel(48))); - } else if (conversation.getMode() == Conversation.MODE_MULTI) { - viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get( - UIHelper.getMessageDisplayName(message), - activity.getPixel(48))); - } - } else if (type == SENT) { - viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get(account, activity.getPixel(48))); + } else { + loadAvatar(message, viewHolder.contact_picture); } viewHolder.contact_picture @@ -463,7 +511,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { public void onClick(View v) { if (MessageAdapter.this.mOnContactPictureClickedListener != null) { MessageAdapter.this.mOnContactPictureClickedListener - .onContactPictureClicked(message); + .onContactPictureClicked(message); } } @@ -475,7 +523,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { public boolean onLongClick(View v) { if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) { MessageAdapter.this.mOnContactPictureLongClickedListener - .onContactPictureLongClicked(message); + .onContactPictureLongClicked(message); return true; } else { return false; @@ -490,7 +538,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { } else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) { displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); } else { - displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first); + displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first,darkBackground); } } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { displayImageMessage(viewHolder, message); @@ -502,10 +550,13 @@ public class MessageAdapter extends ArrayAdapter<Message> { } } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { if (activity.hasPgp()) { - displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message)); + if (account.getPgpDecryptionService().isRunning()) { + displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), darkBackground); + } else { + displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground); + } } else { - displayInfoMessage(viewHolder, - activity.getString(R.string.install_openkeychain)); + displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),darkBackground); if (viewHolder != null) { viewHolder.message_box .setOnClickListener(new OnClickListener() { @@ -518,32 +569,30 @@ public class MessageAdapter extends ArrayAdapter<Message> { } } } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { - displayDecryptionFailed(viewHolder); + displayDecryptionFailed(viewHolder,darkBackground); } else { if (GeoHelper.isGeoUri(message.getBody())) { displayLocationMessage(viewHolder,message); } else if (message.treatAsDownloadable() == Message.Decision.MUST) { displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); } else { - displayTextMessage(viewHolder, message); + displayTextMessage(viewHolder, message, darkBackground); } } - displayStatus(viewHolder, message); - - return view; - } - - public void startDownloadable(Message message) { - Transferable transferable = message.getTransferable(); - if (transferable != null) { - if (!transferable.start()) { - Toast.makeText(activity, R.string.not_connected_try_again, - Toast.LENGTH_SHORT).show(); + if (type == RECEIVED) { + if(isInValidSession) { + viewHolder.encryption.setVisibility(View.GONE); + } else { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); + viewHolder.encryption.setVisibility(View.VISIBLE); + viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption())); } - } else if (message.treatAsDownloadable() != Message.Decision.NEVER) { - activity.xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message); } + + displayStatus(viewHolder, message, type, darkBackground); + + return view; } public void openDownloadable(Message message) { @@ -553,14 +602,23 @@ public class MessageAdapter extends ArrayAdapter<Message> { return; } Intent openIntent = new Intent(Intent.ACTION_VIEW); - openIntent.setDataAndType(Uri.fromFile(file), file.getMimeType()); + String mime = file.getMimeType(); + if (mime == null) { + mime = "*/*"; + } + openIntent.setDataAndType(Uri.fromFile(file), mime); PackageManager manager = activity.getPackageManager(); List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0); - if (infos.size() > 0) { + if (infos.size() == 0) { + openIntent.setDataAndType(Uri.fromFile(file),"*/*"); + } + try { getContext().startActivity(openIntent); - } else { - Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show(); + return; + } catch (ActivityNotFoundException e) { + //ignored } + Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show(); } public void showLocation(Message message) { @@ -574,11 +632,11 @@ public class MessageAdapter extends ArrayAdapter<Message> { } public interface OnContactPictureClicked { - public void onContactPictureClicked(Message message); + void onContactPictureClicked(Message message); } public interface OnContactPictureLongClicked { - public void onContactPictureLongClicked(Message message); + void onContactPictureLongClicked(Message message); } private static class ViewHolder { @@ -589,8 +647,92 @@ public class MessageAdapter extends ArrayAdapter<Message> { protected ImageView indicator; protected ImageView indicatorReceived; protected TextView time; - protected EmojiconTextView messageBody; + protected TextView messageBody; protected ImageView contact_picture; protected TextView status_message; + protected TextView encryption; + } + + class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> { + private final WeakReference<ImageView> imageViewReference; + private Message message = null; + + public BitmapWorkerTask(ImageView imageView) { + imageViewReference = new WeakReference<>(imageView); + } + + @Override + protected Bitmap doInBackground(Message... params) { + return AvatarService.getInstance().get(params[0], activity.getPixel(48), isCancelled()); + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (bitmap != null) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + imageView.setBackgroundColor(0x00000000); + } + } + } + } + + public void loadAvatar(Message message, ImageView imageView) { + if (cancelPotentialWork(message, imageView)) { + final Bitmap bm = AvatarService.getInstance().get(message, activity.getPixel(48), true); + if (bm != null) { + imageView.setImageBitmap(bm); + imageView.setBackgroundColor(0x00000000); + } else { + imageView.setBackgroundColor(UIHelper.getColorForName(UIHelper.getMessageDisplayName(message))); + imageView.setImageDrawable(null); + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task); + imageView.setImageDrawable(asyncDrawable); + try { + task.execute(message); + } catch (final RejectedExecutionException ignored) { + } + } + } + } + + 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); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/PresencesArrayAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/PresencesArrayAdapter.java index 0b5cb897..2f8d12f2 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/PresencesArrayAdapter.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/PresencesArrayAdapter.java @@ -6,7 +6,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; -import android.widget.ImageView; import android.widget.TextView; import java.util.ArrayList; @@ -45,10 +44,10 @@ public class PresencesArrayAdapter extends ArrayAdapter<Presence> { private static Presence[] getPresenceArray(Presences presences) { ArrayList<Presence> presenceArrayList = new ArrayList<>(); if (null != presences && null != presences.getPresences() && !presences.getPresences().isEmpty()) { - for (Map.Entry<String, Integer> entry : presences.getPresences().entrySet()) { + for (Map.Entry<String, de.thedevstack.conversationsplus.entities.Presence> entry : presences.getPresences().entrySet()) { Presence p = new Presence(); p.resource = entry.getKey(); - p.status = entry.getValue(); + p.status = entry.getValue().getStatus(); presenceArrayList.add(p); } presenceArrayList.trimToSize(); @@ -59,5 +58,5 @@ public class PresencesArrayAdapter extends ArrayAdapter<Presence> { class Presence { String resource; - int status; + de.thedevstack.conversationsplus.entities.Presence.Status status; } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/UserDecisionDialog.java b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/UserDecisionDialog.java index ad920934..72d9d904 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/UserDecisionDialog.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/UserDecisionDialog.java @@ -6,9 +6,9 @@ import android.view.View; import android.widget.CheckBox; import android.widget.TextView; -import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.enums.UserDecision; import de.thedevstack.conversationsplus.ui.listeners.UserDecisionListener; +import de.thedevstack.conversationsplus.R; /** * Created by tzur on 31.10.2015. diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormBooleanFieldWrapper.java b/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormBooleanFieldWrapper.java new file mode 100644 index 00000000..04c3fe20 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormBooleanFieldWrapper.java @@ -0,0 +1,80 @@ +package de.thedevstack.conversationsplus.ui.forms; + +import android.content.Context; +import android.widget.CheckBox; +import android.widget.CompoundButton; + +import java.util.ArrayList; +import java.util.List; + +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.xmpp.forms.Field; + +public class FormBooleanFieldWrapper extends FormFieldWrapper { + + protected CheckBox checkBox; + + protected FormBooleanFieldWrapper(Context context, Field field) { + super(context, field); + checkBox = (CheckBox) view.findViewById(R.id.field); + checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + checkBox.setError(null); + invokeOnFormFieldValuesEdited(); + } + }); + } + + @Override + protected void setLabel(String label, boolean required) { + CheckBox checkBox = (CheckBox) view.findViewById(R.id.field); + checkBox.setText(createSpannableLabelString(label, required)); + } + + @Override + public List<String> getValues() { + List<String> values = new ArrayList<>(); + values.add(Boolean.toString(checkBox.isChecked())); + return values; + } + + @Override + protected void setValues(List<String> values) { + if (values.size() == 0) { + checkBox.setChecked(false); + } else { + checkBox.setChecked(Boolean.parseBoolean(values.get(0))); + } + } + + @Override + public boolean validates() { + if (checkBox.isChecked() || !field.isRequired()) { + return true; + } else { + checkBox.setError(context.getString(R.string.this_field_is_required)); + checkBox.requestFocus(); + return false; + } + } + + @Override + public boolean edited() { + if (field.getValues().size() == 0) { + return checkBox.isChecked(); + } else { + return super.edited(); + } + } + + @Override + protected int getLayoutResource() { + return R.layout.form_boolean; + } + + @Override + void setReadOnly(boolean readOnly) { + checkBox.setEnabled(!readOnly); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormFieldFactory.java b/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormFieldFactory.java new file mode 100644 index 00000000..e726b6cc --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormFieldFactory.java @@ -0,0 +1,30 @@ +package de.thedevstack.conversationsplus.ui.forms; + +import android.content.Context; + +import java.util.Hashtable; + +import de.thedevstack.conversationsplus.xmpp.forms.Field; + + + +public class FormFieldFactory { + + private static final Hashtable<String, Class> typeTable = new Hashtable<>(); + + static { + typeTable.put("text-single", FormTextFieldWrapper.class); + typeTable.put("text-multi", FormTextFieldWrapper.class); + typeTable.put("text-private", FormTextFieldWrapper.class); + typeTable.put("jid-single", FormJidSingleFieldWrapper.class); + typeTable.put("boolean", FormBooleanFieldWrapper.class); + } + + protected static FormFieldWrapper createFromField(Context context, Field field) { + Class clazz = typeTable.get(field.getType()); + if (clazz == null) { + clazz = FormTextFieldWrapper.class; + } + return FormFieldWrapper.createFromField(clazz, context, field); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormFieldWrapper.java b/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormFieldWrapper.java new file mode 100644 index 00000000..c82421a2 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormFieldWrapper.java @@ -0,0 +1,93 @@ +package de.thedevstack.conversationsplus.ui.forms; + +import android.content.Context; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.view.LayoutInflater; +import android.view.View; + +import java.util.List; + +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.xmpp.forms.Field; + +public abstract class FormFieldWrapper { + + protected final Context context; + protected final Field field; + protected final View view; + protected OnFormFieldValuesEdited onFormFieldValuesEditedListener; + + protected FormFieldWrapper(Context context, Field field) { + this.context = context; + this.field = field; + LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + this.view = inflater.inflate(getLayoutResource(), null); + String label = field.getLabel(); + if (label == null) { + label = field.getFieldName(); + } + setLabel(label, field.isRequired()); + } + + public final void submit() { + this.field.setValues(getValues()); + } + + public final View getView() { + return view; + } + + protected abstract void setLabel(String label, boolean required); + + abstract List<String> getValues(); + + protected abstract void setValues(List<String> values); + + abstract boolean validates(); + + abstract protected int getLayoutResource(); + + abstract void setReadOnly(boolean readOnly); + + protected SpannableString createSpannableLabelString(String label, boolean required) { + SpannableString spannableString = new SpannableString(label + (required ? " *" : "")); + if (required) { + int start = label.length(); + int end = label.length() + 2; + spannableString.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, end, 0); + spannableString.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.accent)), start, end, 0); + } + return spannableString; + } + + protected void invokeOnFormFieldValuesEdited() { + if (this.onFormFieldValuesEditedListener != null) { + this.onFormFieldValuesEditedListener.onFormFieldValuesEdited(); + } + } + + public boolean edited() { + return !field.getValues().equals(getValues()); + } + + public void setOnFormFieldValuesEditedListener(OnFormFieldValuesEdited listener) { + this.onFormFieldValuesEditedListener = listener; + } + + protected static <F extends FormFieldWrapper> FormFieldWrapper createFromField(Class<F> c, Context context, Field field) { + try { + F fieldWrapper = c.getDeclaredConstructor(Context.class, Field.class).newInstance(context,field); + fieldWrapper.setValues(field.getValues()); + return fieldWrapper; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public interface OnFormFieldValuesEdited { + void onFormFieldValuesEdited(); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormJidSingleFieldWrapper.java b/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormJidSingleFieldWrapper.java new file mode 100644 index 00000000..c86653bf --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormJidSingleFieldWrapper.java @@ -0,0 +1,44 @@ +package de.thedevstack.conversationsplus.ui.forms; + +import android.content.Context; +import android.text.InputType; + +import java.util.List; + +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.xmpp.forms.Field; +import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; + +public class FormJidSingleFieldWrapper extends FormTextFieldWrapper { + + protected FormJidSingleFieldWrapper(Context context, Field field) { + super(context, field); + editText.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + editText.setHint(R.string.account_settings_example_jabber_id); + } + + @Override + public boolean validates() { + String value = getValue(); + if (!value.isEmpty()) { + try { + Jid.fromString(value); + } catch (InvalidJidException e) { + editText.setError(context.getString(R.string.invalid_jid)); + editText.requestFocus(); + return false; + } + } + return super.validates(); + } + + @Override + protected void setValues(List<String> values) { + StringBuilder builder = new StringBuilder(""); + for(String value : values) { + builder.append(value); + } + editText.setText(builder.toString()); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormTextFieldWrapper.java b/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormTextFieldWrapper.java new file mode 100644 index 00000000..f825809c --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormTextFieldWrapper.java @@ -0,0 +1,97 @@ +package de.thedevstack.conversationsplus.ui.forms; + +import android.content.Context; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.widget.EditText; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.xmpp.forms.Field; + +public class FormTextFieldWrapper extends FormFieldWrapper { + + protected EditText editText; + + protected FormTextFieldWrapper(Context context, Field field) { + super(context, field); + editText = (EditText) view.findViewById(R.id.field); + editText.setSingleLine(!"text-multi".equals(field.getType())); + if ("text-private".equals(field.getType())) { + editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + } + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + editText.setError(null); + invokeOnFormFieldValuesEdited(); + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + } + + @Override + protected void setLabel(String label, boolean required) { + TextView textView = (TextView) view.findViewById(R.id.label); + textView.setText(createSpannableLabelString(label, required)); + } + + protected String getValue() { + return editText.getText().toString(); + } + + @Override + public List<String> getValues() { + List<String> values = new ArrayList<>(); + for (String line : getValue().split("\\n")) { + if (line.length() > 0) { + values.add(line); + } + } + return values; + } + + @Override + protected void setValues(List<String> values) { + StringBuilder builder = new StringBuilder(""); + for(int i = 0; i < values.size(); ++i) { + builder.append(values.get(i)); + if (i < values.size() - 1 && "text-multi".equals(field.getType())) { + builder.append("\n"); + } + } + editText.setText(builder.toString()); + } + + @Override + public boolean validates() { + if (getValue().trim().length() > 0 || !field.isRequired()) { + return true; + } else { + editText.setError(context.getString(R.string.this_field_is_required)); + editText.requestFocus(); + return false; + } + } + + @Override + protected int getLayoutResource() { + return R.layout.form_text; + } + + @Override + void setReadOnly(boolean readOnly) { + editText.setEnabled(!readOnly); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormWrapper.java b/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormWrapper.java new file mode 100644 index 00000000..8ff9efae --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/ui/forms/FormWrapper.java @@ -0,0 +1,72 @@ +package de.thedevstack.conversationsplus.ui.forms; + +import android.content.Context; +import android.widget.LinearLayout; + +import java.util.ArrayList; +import java.util.List; + +import de.thedevstack.conversationsplus.xmpp.forms.Data; +import de.thedevstack.conversationsplus.xmpp.forms.Field; + +public class FormWrapper { + + private final LinearLayout layout; + + private final Data form; + + private final List<FormFieldWrapper> fieldWrappers = new ArrayList<>(); + + private FormWrapper(Context context, LinearLayout linearLayout, Data form) { + this.form = form; + this.layout = linearLayout; + this.layout.removeAllViews(); + for(Field field : form.getFields()) { + FormFieldWrapper fieldWrapper = FormFieldFactory.createFromField(context,field); + if (fieldWrapper != null) { + layout.addView(fieldWrapper.getView()); + fieldWrappers.add(fieldWrapper); + } + } + } + + public Data submit() { + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + fieldWrapper.submit(); + } + this.form.submit(); + return this.form; + } + + public boolean validates() { + boolean validates = true; + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + validates &= fieldWrapper.validates(); + } + return validates; + } + + public void setOnFormFieldValuesEditedListener(FormFieldWrapper.OnFormFieldValuesEdited listener) { + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + fieldWrapper.setOnFormFieldValuesEditedListener(listener); + } + } + + public void setReadOnly(boolean b) { + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + fieldWrapper.setReadOnly(b); + } + } + + public boolean edited() { + boolean edited = false; + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + edited |= fieldWrapper.edited(); + } + return edited; + } + + public static FormWrapper createInLayout(Context context, LinearLayout layout, Data form) { + return new FormWrapper(context, layout, form); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ConversationSwipeRefreshListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ConversationSwipeRefreshListener.java index 30b7bf73..5076da28 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ConversationSwipeRefreshListener.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ConversationSwipeRefreshListener.java @@ -8,11 +8,11 @@ import com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayoutD import java.util.List; import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Conversation; -import de.thedevstack.conversationsplus.services.MessageArchiveService; -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.services.MessageArchiveService; import de.thedevstack.conversationsplus.ui.ConversationActivity; import de.thedevstack.conversationsplus.ui.ConversationFragment; import de.thedevstack.conversationsplus.ui.adapter.MessageAdapter; diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java index a9c245ed..1b4c7802 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java @@ -13,19 +13,19 @@ import java.io.InputStream; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.enums.UserDecision; +import de.thedevstack.conversationsplus.exceptions.UiException; +import de.thedevstack.conversationsplus.utils.FileHelper; +import de.thedevstack.conversationsplus.utils.ImageUtil; +import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.enums.UserDecision; -import de.thedevstack.conversationsplus.exceptions.UiException; import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.ui.UiCallback; import de.thedevstack.conversationsplus.ui.XmppActivity; -import de.thedevstack.conversationsplus.utils.FileHelper; -import de.thedevstack.conversationsplus.utils.ImageUtil; -import de.thedevstack.conversationsplus.utils.MessageUtil; /** * Created by tzur on 31.10.2015. @@ -59,6 +59,7 @@ public class ResizePictureUserDecisionListener implements UserDecisionListener { @Override public void error(int error, Message message) { hidePrepareFileToast(); + //TODO Find another way to display an error dialog ResizePictureUserDecisionListener.this.activity.displayErrorDialog(error); } @@ -96,10 +97,10 @@ public class ResizePictureUserDecisionListener implements UserDecisionListener { this.showPrepareFileToast(); final Message message; final boolean forceEncryption = ConversationsPlusPreferences.forceEncryption(); - if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) { + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); } else { - message = new Message(conversation, "", conversation.getNextEncryption(forceEncryption)); + message = new Message(conversation, "", conversation.getNextEncryption()); } message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_IMAGE); @@ -115,7 +116,7 @@ public class ResizePictureUserDecisionListener implements UserDecisionListener { int imageWidth = resizedAndRotatedImage.getWidth(); int imageHeight = resizedAndRotatedImage.getHeight(); MessageUtil.updateMessageWithImageDetails(message, filePath, imageSize, imageWidth, imageHeight); - if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) { + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { xmppConnectionService.getPgpEngine().encrypt(message, callback); } else { callback.success(message); @@ -133,10 +134,10 @@ public class ResizePictureUserDecisionListener implements UserDecisionListener { this.showPrepareFileToast(); final Message message; final boolean forceEncryption = ConversationsPlusPreferences.forceEncryption(); - if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) { + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); } else { - message = new Message(conversation, "", conversation.getNextEncryption(forceEncryption)); + message = new Message(conversation, "", conversation.getNextEncryption()); } message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_IMAGE); @@ -154,7 +155,7 @@ public class ResizePictureUserDecisionListener implements UserDecisionListener { int imageWidth = options.outWidth; String filePath = FileHelper.getRealPathFromUri(uri); MessageUtil.updateMessageWithImageDetails(message, filePath, imageSize, imageWidth, imageHeight); - if (conversation.getNextEncryption(forceEncryption) == Message.ENCRYPTION_PGP) { + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { xmppConnectionService.getPgpEngine().encrypt(message, callback); } else { callback.success(message); diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ShowResourcesListDialogListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ShowResourcesListDialogListener.java index 070ea58c..791b31a7 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ShowResourcesListDialogListener.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ShowResourcesListDialogListener.java @@ -3,10 +3,10 @@ package de.thedevstack.conversationsplus.ui.listeners; import android.content.Context; import android.view.View; -import de.thedevstack.conversationsplus.R; -import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.ui.adapter.PresencesArrayAdapter; import de.thedevstack.conversationsplus.ui.dialogs.AbstractAlertDialog; +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.entities.Contact; /** * This listener shows the dialog with the resources of a contact. diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/widget/Switch.java b/src/main/java/de/thedevstack/conversationsplus/ui/widget/Switch.java new file mode 100644 index 00000000..e5a4f0dc --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/ui/widget/Switch.java @@ -0,0 +1,68 @@ +package de.thedevstack.conversationsplus.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import com.kyleduo.switchbutton.SwitchButton; + +public class Switch extends SwitchButton { + + private int mTouchSlop; + private int mClickTimeout; + private float mStartX; + private float mStartY; + private OnClickListener mOnClickListener; + + public Switch(Context context) { + super(context); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); + } + + public Switch(Context context, AttributeSet attrs) { + super(context, attrs); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); + } + + public Switch(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); + } + + @Override + public void setOnClickListener(OnClickListener onClickListener) { + this.mOnClickListener = onClickListener; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isEnabled()) { + float deltaX = event.getX() - mStartX; + float deltaY = event.getY() - mStartY; + int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + mStartX = event.getX(); + mStartY = event.getY(); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + float time = event.getEventTime() - event.getDownTime(); + if (deltaX < mTouchSlop && deltaY < mTouchSlop && time < mClickTimeout) { + if (mOnClickListener != null) { + this.mOnClickListener.onClick(this); + } + } + break; + default: + break; + } + return true; + } + return super.onTouchEvent(event); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java index 660100a3..206ee668 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java @@ -16,8 +16,8 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.xmpp.pep.Avatar; /** diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/CryptoHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/CryptoHelper.java index 9599aa37..7e4a4fc5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/CryptoHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/CryptoHelper.java @@ -1,20 +1,35 @@ package de.thedevstack.conversationsplus.utils; -import java.security.SecureRandom; +import android.os.Bundle; +import android.util.Pair; + +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; + +import java.security.MessageDigest; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; import java.text.Normalizer; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; public final class CryptoHelper { public static final String FILETRANSFER = "?FILETRANSFERv1:"; private final static char[] hexArray = "0123456789abcdef".toCharArray(); - private final static char[] vowels = "aeiou".toCharArray(); - private final static char[] consonants = "bcdfghjklmnpqrstvwxyz".toCharArray(); final public static byte[] ONE = new byte[] { 0, 0, 0, 1 }; public static String bytesToHex(byte[] bytes) { @@ -48,22 +63,6 @@ public final class CryptoHelper { return result; } - public static String randomMucName(SecureRandom random) { - return randomWord(3, random) + "." + randomWord(7, random); - } - - private 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(); - } - /** * Escapes usernames or passwords for SASL. */ @@ -96,11 +95,18 @@ public final class CryptoHelper { } else if (fingerprint.length() < 40) { return fingerprint; } + StringBuilder builder = new StringBuilder(fingerprint.toLowerCase(Locale.US).replaceAll("\\s", "")); + for(int i=8;i<builder.length();i+=9) { + builder.insert(i, ' '); + } + return builder.toString(); + } + + public static String prettifyFingerprintCert(String fingerprint) { StringBuilder builder = new StringBuilder(fingerprint); - builder.insert(8, " "); - builder.insert(17, " "); - builder.insert(26, " "); - builder.insert(35, " "); + for(int i=2;i < builder.length(); i+=3) { + builder.insert(i,':'); + } return builder.toString(); } @@ -126,4 +132,80 @@ public final class CryptoHelper { } } } + + public static Pair<Jid,String> extractJidAndName(X509Certificate certificate) throws CertificateEncodingException, InvalidJidException, CertificateParsingException { + Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames(); + List<String> emails = new ArrayList<>(); + if (alternativeNames != null) { + for(List<?> san : alternativeNames) { + Integer type = (Integer) san.get(0); + if (type == 1) { + emails.add((String) san.get(1)); + } + } + } + X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); + if (emails.size() == 0) { + emails.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue())); + } + String name = IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue()); + if (emails.size() >= 1) { + return new Pair<>(Jid.fromString(emails.get(0)), name); + } else { + return null; + } + } + + public static Bundle extractCertificateInformation(X509Certificate certificate) { + Bundle information = new Bundle(); + try { + JcaX509CertificateHolder holder = new JcaX509CertificateHolder(certificate); + X500Name subject = holder.getSubject(); + try { + information.putString("subject_cn", subject.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + try { + information.putString("subject_o",subject.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + + X500Name issuer = holder.getIssuer(); + try { + information.putString("issuer_cn", issuer.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + try { + information.putString("issuer_o", issuer.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] fingerprint = md.digest(certificate.getEncoded()); + information.putString("sha1", prettifyFingerprintCert(bytesToHex(fingerprint))); + } catch (Exception e) { + + } + return information; + } catch (CertificateEncodingException e) { + return information; + } + } + + public static int encryptionTypeToText(int encryption) { + switch (encryption) { + case Message.ENCRYPTION_OTR: + return R.string.encryption_choice_otr; + case Message.ENCRYPTION_AXOLOTL: + return R.string.encryption_choice_omemo; + case Message.ENCRYPTION_NONE: + return R.string.encryption_choice_unencrypted; + default: + return R.string.encryption_choice_pgp; + } + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/DNSHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/DNSHelper.java index 9481572d..90618292 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/DNSHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/DNSHelper.java @@ -1,33 +1,50 @@ package de.thedevstack.conversationsplus.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.Data; -import de.measite.minidns.util.NameUtil; +import android.annotation.TargetApi; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.RouteInfo; +import android.os.Build; import java.io.IOException; import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; import java.util.TreeSet; import java.util.regex.Pattern; +import de.measite.minidns.Client; +import de.measite.minidns.DNSMessage; +import de.measite.minidns.Record; +import de.measite.minidns.Record.CLASS; +import de.measite.minidns.Record.TYPE; +import de.measite.minidns.record.Data; +import de.measite.minidns.record.SRV; +import de.measite.minidns.util.NameUtil; import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.dto.SrvRecord; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.xmpp.jid.Jid; public class DNSHelper { private static final String CLIENT_SRV_PREFIX = "_xmpp-client._tcp."; - private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); - private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); + private static final String SECURE_CLIENT_SRV_PREFIX = "_xmpps-client._tcp."; + private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); + private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); protected static Client client = new Client(); + static { + client.setTimeout(Config.PING_TIMEOUT * 1000); + } + /** * Queries the SRV record for the server JID. * This method uses all available Domain Name Servers. @@ -36,12 +53,13 @@ public class DNSHelper { */ public static final TreeSet<SrvRecord> querySrvRecord(Jid jid) { String host = jid.getDomainpart(); - String dns[] = client.findDNS(); TreeSet<SrvRecord> result = new TreeSet<>(); - if (dns != null) { - for (String dnsserver : dns) { - result = querySrvRecord(host, dnsserver); + final List<InetAddress> dnsServers = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDnsServers() : getDnsServersPreLollipop(); + + if (dnsServers != null) { + for (InetAddress dnsServer : dnsServers) { + result = querySrvRecord(host, dnsServer); if (!result.isEmpty()) { break; } @@ -51,51 +69,92 @@ public class DNSHelper { return result; } + @TargetApi(21) + private static List<InetAddress> getDnsServers() { + List<InetAddress> servers = new ArrayList<>(); + ConnectivityManager connectivityManager = (ConnectivityManager) ConversationsPlusApplication.getInstance().getSystemService(Context.CONNECTIVITY_SERVICE); + Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks(); + if (networks == null) { + return getDnsServersPreLollipop(); + } + for(int i = 0; i < networks.length; ++i) { + LinkProperties linkProperties = connectivityManager.getLinkProperties(networks[i]); + if (linkProperties != null) { + if (hasDefaultRoute(linkProperties)) { + servers.addAll(0, linkProperties.getDnsServers()); + } else { + servers.addAll(linkProperties.getDnsServers()); + } + } + } + if (servers.size() > 0) { + Logging.d("dns", "used lollipop variant to discover dns servers in " + networks.length + " networks"); + } + return servers.size() > 0 ? servers : getDnsServersPreLollipop(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static boolean hasDefaultRoute(LinkProperties linkProperties) { + for(RouteInfo route: linkProperties.getRoutes()) { + if (route.isDefaultRoute()) { + return true; + } + } + return false; + } + + private static List<InetAddress> getDnsServersPreLollipop() { + List<InetAddress> servers = new ArrayList<>(); + String[] dns = client.findDNS(); + for(int i = 0; i < dns.length; ++i) { + try { + servers.add(InetAddress.getByName(dns[i])); + } catch (UnknownHostException e) { + //ignore + } + } + return servers; + } + /** * Queries the SRV record for an host from the given Domain Name Server. * @param host the host to query for - * @param dnsserver the DNS to query on + * @param dnsServerAddress the DNS to query on * @return TreeSet with SrvRecords. */ - private static final TreeSet<SrvRecord> querySrvRecord(String host, String dnsserver) { + private static final TreeSet<SrvRecord> querySrvRecord(String host, InetAddress dnsServerAddress) { TreeSet<SrvRecord> result = new TreeSet<>(); + querySrvRecord(host, dnsServerAddress, false, result); + querySrvRecord(host, dnsServerAddress, true, result); + return result; + } + + private static final void querySrvRecord(String host, InetAddress dnsServerAddress, boolean tlsSrvRecord, TreeSet<SrvRecord> result) { + String qname = (tlsSrvRecord ? SECURE_CLIENT_SRV_PREFIX : CLIENT_SRV_PREFIX) + host; + String dnsServerHostAddress = dnsServerAddress.getHostAddress(); + Logging.d("dns", "using dns server: " + dnsServerHostAddress + " to look up " + qname); try { - InetAddress dnsServerAddress = InetAddress.getByName(dnsserver); - String qname = CLIENT_SRV_PREFIX + host; - DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServerAddress.getHostAddress()); + DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServerHostAddress); Record[] rrset = message.getAnswers(); for (Record rr : rrset) { Data d = rr.getPayload(); if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) { SRV srv = (SRV) d; - SrvRecord srvRecord = new SrvRecord(srv.getPriority(), srv.getName(), srv.getPort()); + SrvRecord srvRecord = new SrvRecord(srv.getPriority(), srv.getName(), srv.getPort(), tlsSrvRecord); result.add(srvRecord); } } } catch (IOException e) { - Logging.d("dns", "Error while retrieving SRV record for '" + host + "' from DNS '" + dnsserver + "': " + e.getMessage()); + Logging.d("dns", "Error while retrieving SRV record '" + qname + "' for '" + host + "' from DNS '" + dnsServerHostAddress + "': " + e.getMessage()); } - return result; } - /** - * Checks whether the given server is an IP address or not. - * The following patterns are treated as valid IP addresses: - * <ul> - * <li>{@link #PATTERN_IPV4}</li> - * <li>{@link #PATTERN_IPV6}</li> - * <li>{@link #PATTERN_IPV6_6HEX4DEC}</li> - * <li>{@link #PATTERN_IPV6_HEX4DECCOMPRESSED}</li> - * <li>{@link #PATTERN_IPV6_HEXCOMPRESSED}</li> - * </ul> - * @param server the string to check - * @return <code>true</code> if one of the patterns is matched <code>false</code> otherwise - */ public static boolean isIp(final String server) { - return PATTERN_IPV4.matcher(server).matches() + return server != null && ( + PATTERN_IPV4.matcher(server).matches() || PATTERN_IPV6.matcher(server).matches() || PATTERN_IPV6_6HEX4DEC.matcher(server).matches() || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches() - || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches(); + || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches()); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHandler.java b/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHandler.java index 256287f1..a810b089 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHandler.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHandler.java @@ -1,5 +1,7 @@ package de.thedevstack.conversationsplus.utils; +import android.content.Context; + import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; @@ -8,8 +10,6 @@ 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; diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHelper.java index 32f45127..913f2ab5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHelper.java @@ -1,5 +1,14 @@ package de.thedevstack.conversationsplus.utils; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.text.format.DateUtils; + import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; @@ -7,27 +16,17 @@ import java.io.InputStreamReader; import java.util.List; import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.services.XmppConnectionService; +import de.thedevstack.conversationsplus.ui.ConversationActivity; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; -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; - public class ExceptionHelper { public static void init(Context context) { if (!(Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler)) { @@ -36,12 +35,11 @@ public class ExceptionHelper { } } - public static void checkForCrash(final Context context, - final XmppConnectionService service) { + public static boolean checkForCrash(final ConversationActivity activity, final XmppConnectionService service) { try { boolean neverSend = ConversationsPlusPreferences.neverSend(); if (neverSend) { - return; + return false; } List<Account> accounts = service.getAccounts(); Account account = null; @@ -52,24 +50,25 @@ public class ExceptionHelper { } } if (account == null) { - return; + return false; } final Account finalAccount = account; - FileInputStream file = context.openFileInput("stacktrace.txt"); + FileInputStream file = activity.openFileInput("stacktrace.txt"); InputStreamReader inputStreamReader = new InputStreamReader(file); BufferedReader stacktrace = new BufferedReader(inputStreamReader); final StringBuilder report = new StringBuilder(); - PackageManager pm = context.getPackageManager(); + PackageManager pm = activity.getPackageManager(); PackageInfo packageInfo = null; try { - packageInfo = pm.getPackageInfo(context.getPackageName(), 0); + packageInfo = pm.getPackageInfo(activity.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'); + + DateUtils.formatDateTime(activity, + packageInfo.lastUpdateTime, + DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_DATE) + '\n'); } catch (NameNotFoundException e) { + return false; } String line; while ((line = stacktrace.readLine()) != null) { @@ -77,11 +76,11 @@ public class ExceptionHelper { 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), + activity.deleteFile("stacktrace.txt"); + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(activity.getString(R.string.crash_report_title)); + builder.setMessage(activity.getText(R.string.crash_report_message)); + builder.setPositiveButton(activity.getText(R.string.send_now), new OnClickListener() { @Override @@ -90,18 +89,18 @@ public class ExceptionHelper { Logging.d(Config.LOGTAG, "using account=" + finalAccount.getJid().toBareJid() + " to send in stack trace"); - Conversation conversation = null; - try { - conversation = service.findOrCreateConversation(finalAccount, - Jid.fromString(context.getString(R.string.cplus_bugreport_jabberid)), false); - } catch (final InvalidJidException ignored) { - } - Message message = new Message(conversation, report + Conversation conversation = null; + try { + conversation = service.findOrCreateConversation(finalAccount, + Jid.fromString(activity.getString(R.string.cplus_bugreport_jabberid)), false); + } catch (final InvalidJidException ignored) { + } + Message message = new Message(conversation, report .toString(), Message.ENCRYPTION_NONE); service.sendMessage(message); } }); - builder.setNegativeButton(context.getText(R.string.send_never), + builder.setNegativeButton(activity.getText(R.string.send_never), new OnClickListener() { @Override @@ -110,8 +109,9 @@ public class ExceptionHelper { } }); builder.create().show(); + return true; } catch (final IOException ignored) { - } - + return false; + } } } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ExifHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/ExifHelper.java index 576698ce..031bdfa5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/ExifHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ExifHelper.java @@ -16,11 +16,11 @@ package de.thedevstack.conversationsplus.utils; -import de.thedevstack.android.logcat.Logging; - import java.io.IOException; import java.io.InputStream; +import de.thedevstack.android.logcat.Logging; + public class ExifHelper { private static final String TAG = "CameraExif"; diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java b/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java new file mode 100644 index 00000000..2cf5679b --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java @@ -0,0 +1,149 @@ +package de.thedevstack.conversationsplus.utils; + +import android.annotation.SuppressLint; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; + +public class FileUtils { + + /** + * Get a file path from a Uri. This will get the the path for Storage Access + * Framework Documents, as well as the _data field for the MediaStore and + * other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @author paulburke + */ + @SuppressLint("NewApi") + public static String getPath(final Context context, final Uri uri) { + if (uri == null) { + return null; + } + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + + // TODO handle non-primary volumes + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{ + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + public static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } catch(Exception e) { + return null; + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java index 9250c432..bf79d7e0 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java @@ -11,16 +11,14 @@ import android.util.LruCache; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.exceptions.ImageResizeException; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.exceptions.FileCopyException; -import de.thedevstack.conversationsplus.exceptions.ImageResizeException; import de.thedevstack.conversationsplus.persistance.FileBackend; /** diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java index c04ebdb8..7afc3fa4 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java @@ -3,6 +3,8 @@ package de.thedevstack.conversationsplus.utils; import android.graphics.BitmapFactory; import java.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.Message; @@ -12,6 +14,26 @@ import de.thedevstack.conversationsplus.persistance.FileBackend; * Created by tzur on 15.12.2015. */ public final class MessageUtil { + public static boolean wasHighlightedOrPrivate(final Message message) { + final String nick = message.getConversation().getMucOptions().getActualNick(); + final Pattern highlight = generateNickHighlightPattern(nick); + if (message.getBody() == null || nick == null) { + return false; + } + final Matcher m = highlight.matcher(message.getBody()); + return (m.find() || message.getType() == Message.TYPE_PRIVATE); + } + + private static Pattern generateNickHighlightPattern(final 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" + Pattern.quote(nick) + "\\p{Punct}?\\b", + Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); + } + public static void updateMessageWithImageDetails(Message message, String filePath, long size, int imageWidth, int imageHeight) { message.setRelativeFilePath(filePath); MessageUtil.updateMessageBodyWithImageParams(message, size, imageWidth, imageHeight); diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/MimeUtils.java b/src/main/java/de/thedevstack/conversationsplus/utils/MimeUtils.java index c1c40f92..fdf55d2d 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/MimeUtils.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/MimeUtils.java @@ -458,7 +458,7 @@ public final class MimeUtils { if (extension == null || extension.isEmpty()) { return null; } - return extensionToMimeTypeMap.get(extension); + return extensionToMimeTypeMap.get(extension.toLowerCase()); } /** * Returns true if the given extension has a registered MIME type. diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/OnPhoneContactsLoadedListener.java b/src/main/java/de/thedevstack/conversationsplus/utils/OnPhoneContactsLoadedListener.java index e701d62b..eecee159 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/OnPhoneContactsLoadedListener.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/OnPhoneContactsLoadedListener.java @@ -1,9 +1,9 @@ package de.thedevstack.conversationsplus.utils; -import java.util.List; - import android.os.Bundle; +import java.util.List; + public interface OnPhoneContactsLoadedListener { public void onPhoneContactsLoaded(List<Bundle> phoneContacts); } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/PhoneHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/PhoneHelper.java index d1f598c0..86a67661 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/PhoneHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/PhoneHelper.java @@ -1,8 +1,6 @@ package de.thedevstack.conversationsplus.utils; -import java.util.List; -import java.util.concurrent.RejectedExecutionException; - +import android.Manifest; import android.content.Context; import android.content.CursorLoader; import android.content.Loader; @@ -10,20 +8,27 @@ import android.content.Loader.OnLoadCompleteListener; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.provider.ContactsContract; import android.provider.ContactsContract.Profile; -import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import java.util.List; +import java.util.concurrent.RejectedExecutionException; public class PhoneHelper { - public static void loadPhoneContacts(Context context,final List<Bundle> phoneContacts, final OnPhoneContactsLoadedListener listener) { - final String[] PROJECTION = new String[] { ContactsContract.Data._ID, + public static void loadPhoneContacts(Context context, final List<Bundle> phoneContacts, final OnPhoneContactsLoadedListener listener) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + listener.onPhoneContactsLoaded(phoneContacts); + return; + } + final String[] PROJECTION = new String[]{ContactsContract.Data._ID, ContactsContract.Data.DISPLAY_NAME, ContactsContract.Data.PHOTO_URI, ContactsContract.Data.LOOKUP_KEY, - ContactsContract.CommonDataKinds.Im.DATA }; + ContactsContract.CommonDataKinds.Im.DATA}; final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\"" + ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE @@ -31,39 +36,39 @@ public class PhoneHelper { + "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER + "\")"; - CursorLoader mCursorLoader = new CursorLoader(context, + CursorLoader mCursorLoader = new NotThrowCursorLoader(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 (cursor != null) { + 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); + } + cursor.close(); } + if (listener != null) { listener.onPhoneContactsLoaded(phoneContacts); } - cursor.close(); } }); try { @@ -75,8 +80,30 @@ public class PhoneHelper { } } + private static class NotThrowCursorLoader extends CursorLoader { + + public NotThrowCursorLoader(Context c, Uri u, String[] p, String s, String[] sa, String so) { + super(c, u, p, s, sa, so); + } + + @Override + public Cursor loadInBackground() { + + try { + return (super.loadInBackground()); + } catch (SecurityException e) { + return(null); + } + } + + } + public static Uri getSefliUri(Context context) { - String[] mProjection = new String[] { Profile._ID, Profile.PHOTO_URI }; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + return null; + } + String[] mProjection = new String[]{Profile._ID, Profile.PHOTO_URI}; Cursor mProfileCursor = context.getContentResolver().query( Profile.CONTENT_URI, mProjection, null, null, null); @@ -93,4 +120,17 @@ public class PhoneHelper { } } } + + public static String getVersionName(Context context) { + final String packageName = context == null ? null : context.getPackageName(); + if (packageName != null) { + try { + return context.getPackageManager().getPackageInfo(packageName, 0).versionName; + } catch (final PackageManager.NameNotFoundException | RuntimeException e) { + return "unknown"; + } + } else { + return "unknown"; + } + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/SSLSocketHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/SSLSocketHelper.java new file mode 100644 index 00000000..1842e897 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/utils/SSLSocketHelper.java @@ -0,0 +1,73 @@ +package de.thedevstack.conversationsplus.utils; + +import android.os.Build; + +import java.lang.reflect.Method; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +public class SSLSocketHelper { + + public static void setSecurity(final SSLSocket sslSocket) throws NoSuchAlgorithmException { + final String[] supportProtocols; + final Collection<String> supportedProtocols = new LinkedList<>( + Arrays.asList(sslSocket.getSupportedProtocols())); + supportedProtocols.remove("SSLv3"); + supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]); + + sslSocket.setEnabledProtocols(supportProtocols); + + final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( + sslSocket.getSupportedCipherSuites()); + if (cipherSuites.length > 0) { + sslSocket.setEnabledCipherSuites(cipherSuites); + } + } + + public static void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) { + if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { + ((android.net.SSLCertificateSocketFactory) factory).setHostname(socket, hostname); + } else { + try { + socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname); + } catch (Throwable e) { + // ignore any error, we just can't set the hostname... + } + } + } + + public static void setAlpnProtocol(final SSLSocketFactory factory, final SSLSocket socket, final String protocol) { + try { + if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + // can't call directly because of @hide? + //((android.net.SSLCertificateSocketFactory)factory).setAlpnProtocols(new byte[][]{protocol.getBytes("UTF-8")}); + android.net.SSLCertificateSocketFactory.class.getMethod("setAlpnProtocols", byte[][].class).invoke(socket, new Object[]{new byte[][]{protocol.getBytes("UTF-8")}}); + } else { + final Method method = socket.getClass().getMethod("setAlpnProtocols", byte[].class); + // the concatenation of 8-bit, length prefixed protocol names, just one in our case... + // http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 + final byte[] protocolUTF8Bytes = protocol.getBytes("UTF-8"); + final byte[] lengthPrefixedProtocols = new byte[protocolUTF8Bytes.length + 1]; + lengthPrefixedProtocols[0] = (byte) protocol.length(); // cannot be over 255 anyhow + System.arraycopy(protocolUTF8Bytes, 0, lengthPrefixedProtocols, 1, protocolUTF8Bytes.length); + method.invoke(socket, new Object[]{lengthPrefixedProtocols}); + } + } catch (Throwable e) { + // ignore any error, we just can't set the alpn protocol... + } + } + + public static SSLContext getSSLContext() throws NoSuchAlgorithmException { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + return SSLContext.getInstance("TLSv1.2"); + } else { + return SSLContext.getInstance("TLS"); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/SerialSingleThreadExecutor.java b/src/main/java/de/thedevstack/conversationsplus/utils/SerialSingleThreadExecutor.java index 264645c6..4871af43 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/SerialSingleThreadExecutor.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/SerialSingleThreadExecutor.java @@ -31,4 +31,4 @@ public class SerialSingleThreadExecutor implements Executor { executor.execute(active); } } -} +}
\ No newline at end of file diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/SocksSocketFactory.java b/src/main/java/de/thedevstack/conversationsplus/utils/SocksSocketFactory.java new file mode 100644 index 00000000..940399c3 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/utils/SocksSocketFactory.java @@ -0,0 +1,52 @@ +package de.thedevstack.conversationsplus.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; + +import de.thedevstack.conversationsplus.Config; + +public class SocksSocketFactory { + + public static void createSocksConnection(Socket socket, String destination, int port) throws IOException { + InputStream proxyIs = socket.getInputStream(); + OutputStream proxyOs = socket.getOutputStream(); + proxyOs.write(new byte[]{0x05, 0x01, 0x00}); + byte[] response = new byte[2]; + proxyIs.read(response); + byte[] dest = destination.getBytes(); + ByteBuffer request = ByteBuffer.allocate(7 + dest.length); + request.put(new byte[]{0x05, 0x01, 0x00, 0x03}); + request.put((byte) dest.length); + request.put(dest); + request.putShort((short) port); + proxyOs.write(request.array()); + response = new byte[7 + dest.length]; + proxyIs.read(response); + if (response[1] != 0x00) { + throw new SocksConnectionException(); + } + } + + public static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException { + Socket socket = new Socket(); + try { + socket.connect(address, Config.CONNECT_TIMEOUT * 1000); + } catch (IOException e) { + throw new SocksProxyNotFoundException(); + } + createSocksConnection(socket, destination, port); + return socket; + } + + static class SocksConnectionException extends IOException { + + } + + public static class SocksProxyNotFoundException extends IOException { + + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/StreamUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/StreamUtil.java index 64f46314..729bdf11 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/StreamUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/StreamUtil.java @@ -6,6 +6,7 @@ import java.io.Closeable; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.net.Socket; import de.thedevstack.conversationsplus.ConversationsPlusApplication; @@ -40,6 +41,20 @@ public final class StreamUtil { } /** + * Closes a socket. + * IOException is silently ignored. + * @param socket the socket to close + */ + public static void close(Socket socket) { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + } + } + } + + /** * Avoid instantiation of util class. */ private StreamUtil() { diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java index 532ff1cb..e1915e99 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java @@ -1,44 +1,27 @@ package de.thedevstack.conversationsplus.utils; +import android.content.Context; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.Pair; + import java.math.BigDecimal; -import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; -import java.net.URLConnection; import java.util.Calendar; import java.util.Date; -import java.util.HashMap; import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.regex.Pattern; -import java.util.regex.Matcher; -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.Conversation; -import de.thedevstack.conversationsplus.entities.Presences; -import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.Presence; +import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.xmpp.jid.Jid; -import android.content.Context; -import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.text.Spannable.Factory; -import android.text.style.ImageSpan; -import android.text.Spannable; -import android.util.Pair; - public class UIHelper { - private static String BLACK_HEART_SUIT = "\u2665"; - private static String HEAVY_BLACK_HEART_SUIT = "\u2764"; - private static String WHITE_HEART_SUIT = "\u2661"; - - public static final ArrayList<String> HEARTS = new ArrayList<>(Arrays.asList(BLACK_HEART_SUIT,HEAVY_BLACK_HEART_SUIT,WHITE_HEART_SUIT)); - private static final ArrayList<String> LOCATION_QUESTIONS = new ArrayList<>(Arrays.asList( "where are you", //en "where are you now", //en @@ -144,7 +127,7 @@ public class UIHelper { } public static int getColorForName(String name) { - if (name.isEmpty()) { + if (name == null || name.isEmpty()) { return 0xFF202020; } int colors[] = {0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5, @@ -184,7 +167,9 @@ public class UIHelper { return new Pair<>("",false); } } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { - return new Pair<>(context.getString(R.string.encrypted_message_received),true); + return new Pair<>(context.getString(R.string.pgp_message),true); + } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { + return new Pair<>(context.getString(R.string.decryption_failed), true); } else if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) { if (message.getStatus() == Message.STATUS_RECEIVED) { return new Pair<>(context.getString(R.string.received_x_file, @@ -254,15 +239,15 @@ public class UIHelper { } } - public static String getStatusColor(int status) { + public static String getStatusColor(Presence.Status status) { switch (status) { - case Presences.ONLINE: - case Presences.CHAT: + case ONLINE: + case CHAT: return "#259B23"; - case Presences.AWAY: - case Presences.XA: + case AWAY: + case XA: return "#FF9800"; - case Presences.DND: + case DND: return "#E51C23"; } return "#CCCCCC"; diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/Xmlns.java b/src/main/java/de/thedevstack/conversationsplus/utils/Xmlns.java index 85f99c30..f937e35f 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/Xmlns.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/Xmlns.java @@ -1,9 +1,11 @@ package de.thedevstack.conversationsplus.utils; +import de.thedevstack.conversationsplus.Config; + public final class Xmlns { public static final String BLOCKING = "urn:xmpp:blocking"; public static final String ROSTER = "jabber:iq:roster"; public static final String REGISTER = "jabber:iq:register"; public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams"; - public static final String HTTP_UPLOAD = "urn:xmpp:http:upload"; + public static final String HTTP_UPLOAD = Config.LEGACY_NAMESPACE_HTTP_UPLOAD ? "eu:siacs:conversations:http:upload" : "urn:xmpp:http:upload"; } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java index eb3c5da4..11d05832 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java @@ -4,6 +4,7 @@ import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; import de.thedevstack.conversationsplus.xmpp.XmppConnection; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; +import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket; import de.thedevstack.conversationsplus.xmpp.stanzas.PresencePacket; /** @@ -23,4 +24,11 @@ public class XmppSendUtil { connection.sendPresencePacket(packet); } } + + public static void sendMessagePacket(Account account, MessagePacket packet) { + XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + connection.sendMessagePacket(packet); + } + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/xml/Element.java b/src/main/java/de/thedevstack/conversationsplus/xml/Element.java index 3d2c75b0..429f98db 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xml/Element.java +++ b/src/main/java/de/thedevstack/conversationsplus/xml/Element.java @@ -11,15 +11,20 @@ import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; public class Element { - protected String name; - protected Hashtable<String, String> attributes = new Hashtable<>(); - protected String content; + private final String name; + private Hashtable<String, String> attributes = new Hashtable<>(); + private String content; protected List<Element> children = new ArrayList<>(); public Element(String name) { this.name = name; } + public Element(String name, String xmlns) { + this.name = name; + this.setAttribute("xmlns", xmlns); + } + public Element addChild(Element child) { this.content = null; children.add(child); @@ -63,10 +68,9 @@ public class Element { public Element findChild(String name, String xmlns) { for (Element child : this.children) { - if (child.getName().equals(name) - && (child.getAttribute("xmlns").equals(xmlns))) { + if (name.equals(child.getName()) && xmlns.equals(child.getAttribute("xmlns"))) { return child; - } + } } return null; } @@ -93,7 +97,7 @@ public class Element { return this; } - public String getContent() { + public final String getContent() { return content; } @@ -157,7 +161,7 @@ public class Element { return elementOutput.toString(); } - public String getName() { + public final String getName() { return name; } @@ -177,4 +181,8 @@ public class Element { String attr = getAttribute(name); return (attr != null && (attr.equalsIgnoreCase("true") || attr.equalsIgnoreCase("1"))); } + + public String getNamespace() { + return getAttribute("xmlns"); + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/xml/XmlReader.java b/src/main/java/de/thedevstack/conversationsplus/xml/XmlReader.java index 35425816..e02f4fc0 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xml/XmlReader.java +++ b/src/main/java/de/thedevstack/conversationsplus/xml/XmlReader.java @@ -1,19 +1,19 @@ package de.thedevstack.conversationsplus.xml; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.Config; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.util.Xml; - public class XmlReader { private XmlPullParser parser; private PowerManager.WakeLock wakeLock; diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/OnKeyStatusUpdated.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/OnKeyStatusUpdated.java new file mode 100644 index 00000000..47bcff9e --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/OnKeyStatusUpdated.java @@ -0,0 +1,7 @@ +package de.thedevstack.conversationsplus.xmpp; + +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; + +public interface OnKeyStatusUpdated { + public void onKeyStatusUpdated(AxolotlService.FetchStatus report); +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java index d5ab9f90..29be84f5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java @@ -1,66 +1,80 @@ package de.thedevstack.conversationsplus.xmpp; -import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemClock; +import android.security.KeyChain; +import android.util.Base64; +import android.util.Log; import android.util.Pair; import android.util.SparseArray; -import org.apache.http.conn.ssl.StrictHostnameVerifier; import org.json.JSONException; import org.json.JSONObject; import org.xmlpull.v1.XmlPullParserException; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.math.BigInteger; import java.net.ConnectException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; +import java.net.URL; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; -import java.util.LinkedList; +import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Map.Entry; import java.util.TreeSet; import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; +import de.duenndns.ssl.MemorizingTrustManager; import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.dto.SrvRecord; import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.crypto.XmppDomainVerifier; import de.thedevstack.conversationsplus.crypto.sasl.DigestMd5; +import de.thedevstack.conversationsplus.crypto.sasl.External; import de.thedevstack.conversationsplus.crypto.sasl.Plain; import de.thedevstack.conversationsplus.crypto.sasl.SaslMechanism; import de.thedevstack.conversationsplus.crypto.sasl.ScramSha1; -import de.thedevstack.conversationsplus.dto.SrvRecord; import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.ServiceDiscoveryResult; import de.thedevstack.conversationsplus.generator.IqGenerator; import de.thedevstack.conversationsplus.services.XmppConnectionService; -import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.DNSHelper; +import de.thedevstack.conversationsplus.utils.SSLSocketHelper; import de.thedevstack.conversationsplus.utils.Xmlns; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xml.Tag; import de.thedevstack.conversationsplus.xml.TagWriter; import de.thedevstack.conversationsplus.xml.XmlReader; +import de.thedevstack.conversationsplus.xmpp.forms.Data; +import de.thedevstack.conversationsplus.xmpp.forms.Field; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.xmpp.jingle.OnJinglePacketReceived; import de.thedevstack.conversationsplus.xmpp.jingle.stanzas.JinglePacket; +import de.thedevstack.conversationsplus.xmpp.stanzas.AbstractAcknowledgeableStanza; import de.thedevstack.conversationsplus.xmpp.stanzas.AbstractStanza; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket; @@ -83,14 +97,14 @@ public class XmppConnection implements Runnable { private XmlReader tagReader; private TagWriter tagWriter; private final Features features = new Features(this); - private boolean shouldBind = true; + private boolean needsBinding = true; private boolean shouldAuthenticate = true; private Element streamFeatures; - private final HashMap<Jid, Info> disco = new HashMap<>(); + private final HashMap<Jid, ServiceDiscoveryResult> disco = new HashMap<>(); private String streamId = null; private int smVersion = 3; - private final SparseArray<String> messageReceipts = new SparseArray<>(); + private final SparseArray<AbstractAcknowledgeableStanza> mStanzaQueue = new SparseArray<>(); private int stanzasReceived = 0; private int stanzasSent = 0; @@ -98,8 +112,12 @@ public class XmppConnection implements Runnable { private long lastPingSent = 0; private long lastConnect = 0; private long lastSessionStarted = 0; + private long lastDiscoStarted = 0; + private int mPendingServiceDiscoveries = 0; + private final ArrayList<String> mPendingServiceDiscoveriesIds = new ArrayList<>(); + private boolean mInteractive = false; private int attempt = 0; - private final Map<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks = new Hashtable<>(); + private final Hashtable<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks = new Hashtable<>(); private OnPresencePacketReceived presenceListener = null; private OnJinglePacketReceived jingleListener = null; private OnIqPacketReceived unregisteredIqListener = null; @@ -112,6 +130,68 @@ public class XmppConnection implements Runnable { private SaslMechanism saslMechanism; + private X509KeyManager mKeyManager = new X509KeyManager() { + @Override + public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { + return account.getPrivateKeyAlias(); + } + + @Override + public String chooseServerAlias(String s, Principal[] principals, Socket socket) { + return null; + } + + @Override + public X509Certificate[] getCertificateChain(String alias) { + try { + return KeyChain.getCertificateChain(mXmppConnectionService, alias); + } catch (Exception e) { + return new X509Certificate[0]; + } + } + + @Override + public String[] getClientAliases(String s, Principal[] principals) { + return new String[0]; + } + + @Override + public String[] getServerAliases(String s, Principal[] principals) { + return new String[0]; + } + + @Override + public PrivateKey getPrivateKey(String alias) { + try { + return KeyChain.getPrivateKey(mXmppConnectionService, alias); + } catch (Exception e) { + return null; + } + } + }; + private Identity mServerIdentity = Identity.UNKNOWN; + + private OnIqPacketReceived createPacketReceiveHandler() { + return new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + account.setOption(Account.OPTION_REGISTER, + false); + changeStatus(Account.State.REGISTRATION_SUCCESSFUL); + } else if (packet.hasChild("error") + && (packet.findChild("error") + .hasChild("conflict"))) { + changeStatus(Account.State.REGISTRATION_CONFLICT); + } else { + changeStatus(Account.State.REGISTRATION_FAILED); + Log.d(Config.LOGTAG, packet.toString()); + } + disconnect(true); + } + }; + } + public XmppConnection(final Account account, final XmppConnectionService service) { this.account = account; this.wakeLock = service.getPowerManager().newWakeLock( @@ -143,59 +223,92 @@ public class XmppConnection implements Runnable { features.encryptionEnabled = false; lastConnect = SystemClock.elapsedRealtime(); lastPingSent = SystemClock.elapsedRealtime(); + lastDiscoStarted = Long.MAX_VALUE; this.attempt++; + switch (account.getJid().getDomainpart()) { + case "chat.facebook.com": + mServerIdentity = Identity.FACEBOOK; + break; + case "nimbuzz.com": + mServerIdentity = Identity.NIMBUZZ; + break; + default: + mServerIdentity = Identity.UNKNOWN; + break; + } try { - shouldAuthenticate = shouldBind = !account.isOptionSet(Account.OPTION_REGISTER); + shouldAuthenticate = needsBinding = !account.isOptionSet(Account.OPTION_REGISTER); tagReader = new XmlReader(wakeLock); tagWriter = new TagWriter(); - packetCallbacks.clear(); this.changeStatus(Account.State.CONNECTING); - if (DNSHelper.isIp(account.getServer().toString())) { + final boolean extended = mXmppConnectionService.showExtendedConnectionOptions(); + if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) { + socket = new Socket(); + try { + socket.connect(new InetSocketAddress(account.getHostname(), account.getPort()), Config.SOCKET_TIMEOUT * 1000); + } catch (IOException e) { + throw new UnknownHostException(); + } + startXmpp(); + } else if (DNSHelper.isIp(account.getServer().toString())) { socket = new Socket(); try { socket.connect(new InetSocketAddress(account.getServer().toString(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000); } catch (IOException e) { throw new UnknownHostException(); } + startXmpp(); } else { - socket = new Socket(); - TreeSet<SrvRecord> result = DNSHelper.querySrvRecord(account.getServer()); - if (!result.isEmpty()) { - for (SrvRecord record : result) { - try { - socket.connect(new InetSocketAddress(record.getName(), record.getPort()), Config.SOCKET_TIMEOUT * 1000); - break; - } catch (IOException e) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage()); - } - } - } - if (result.isEmpty() || !socket.isConnected()){ + final TreeSet<SrvRecord> srvRecords = DNSHelper.querySrvRecord(account.getServer()); + if (srvRecords.isEmpty()) { + socket = new Socket(); try { socket.connect(new InetSocketAddress(account.getServer().getDomainpart(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000); } catch (IOException e) { throw new UnknownHostException(); } + startXmpp(); + } else { + for (SrvRecord srvRecord : srvRecords) { + // if tls is true, encryption is implied and must not be started + features.encryptionEnabled = srvRecord.isUseTls(); + TlsFactoryVerifier tlsFactoryVerifier = null; + if (features.encryptionEnabled) { + try { + tlsFactoryVerifier = getTlsFactoryVerifier(); + socket = tlsFactoryVerifier.factory.createSocket(); + + if (socket == null) { + throw new IOException("could not initialize ssl socket"); + } + + SSLSocketHelper.setSecurity((SSLSocket) socket); + SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart()); + SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client"); + } catch (SecurityException e) { + throw e; + } catch (KeyManagementException e) { + Logging.e("connection-init", "Error while creating TLS verifier factory: " + e.getMessage(), e); + throw new SecurityException(); + } + } else { + socket = new Socket(); + } + + socket.connect(new InetSocketAddress(srvRecord.getName(), srvRecord.getPort()), Config.SOCKET_TIMEOUT * 1000); + + if (null != tlsFactoryVerifier && !tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); + throw new SecurityException(); + } + + if (startXmpp()) { + break; // successfully connected to server that speaks xmpp + } + } } } - final OutputStream out = socket.getOutputStream(); - tagWriter.setOutputStream(out); - final InputStream in = socket.getInputStream(); - tagReader.setInputStream(in); - tagWriter.beginDocument(); - sendStartStream(); - Tag nextTag; - while ((nextTag = tagReader.readTag()) != null) { - if (nextTag.isStart("stream")) { - processStream(nextTag); - break; - } else { - throw new IOException("unknown tag on connect"); - } - } - if (socket.isConnected()) { - socket.close(); - } + processStream(); } catch (final IncompatibleServerException e) { this.changeStatus(Account.State.INCOMPATIBLE_SERVER); } catch (final SecurityException e) { @@ -209,13 +322,7 @@ public class XmppConnection implements Runnable { this.changeStatus(Account.State.OFFLINE); this.attempt--; //don't count attempt when reconnecting instantly anyway } finally { - if (socket != null) { - try { - socket.close(); - } catch (IOException e) { - - } - } + forceCloseSocket(); if (wakeLock.isHeld()) { try { wakeLock.release(); @@ -225,165 +332,213 @@ public class XmppConnection implements Runnable { } } - @Override - public void run() { - try { - if (socket != null) { - socket.close(); + /** + * Starts xmpp protocol, call after connecting to socket + * @return true if server returns with valid xmpp, false otherwise + * @throws IOException Unknown tag on connect + * @throws XmlPullParserException Bad Xml + * @throws NoSuchAlgorithmException Other error + */ + private boolean startXmpp() throws IOException, XmlPullParserException, NoSuchAlgorithmException { + tagWriter.setOutputStream(socket.getOutputStream()); + tagReader.setInputStream(socket.getInputStream()); + tagWriter.beginDocument(); + sendStartStream(); + Tag nextTag; + while ((nextTag = tagReader.readTag()) != null) { + if (nextTag.isStart("stream")) { + return true; + } else { + throw new IOException("unknown tag on connect"); + } + } + if (socket.isConnected()) { + socket.close(); + } + return false; + } + + private static class TlsFactoryVerifier { + private final SSLSocketFactory factory; + private final HostnameVerifier verifier; + + public TlsFactoryVerifier(final SSLSocketFactory factory, final HostnameVerifier verifier) throws IOException { + this.factory = factory; + this.verifier = verifier; + if (factory == null || verifier == null) { + throw new IOException("could not setup ssl"); } - } catch (final IOException ignored) { + } + } + private TlsFactoryVerifier getTlsFactoryVerifier() throws NoSuchAlgorithmException, KeyManagementException, IOException { + final SSLContext sc = SSLSocketHelper.getSSLContext(); + MemorizingTrustManager trustManager = this.mXmppConnectionService.getMemorizingTrustManager(); + KeyManager[] keyManager; + if (account.getPrivateKeyAlias() != null && account.getPassword().isEmpty()) { + keyManager = new KeyManager[]{mKeyManager}; + } else { + keyManager = null; + } + sc.init(keyManager, new X509TrustManager[]{mInteractive ? trustManager : trustManager.getNonInteractive()}, mXmppConnectionService.getRNG()); + final SSLSocketFactory factory = sc.getSocketFactory(); + final HostnameVerifier verifier; + if (mInteractive) { + verifier = trustManager.wrapHostnameVerifier(new XmppDomainVerifier()); + } else { + verifier = trustManager.wrapHostnameVerifierNonInteractive(new XmppDomainVerifier()); } + + return new TlsFactoryVerifier(factory, verifier); + } + + @Override + public void run() { + forceCloseSocket(); connect(); } - private void processStream(final Tag currentTag) throws XmlPullParserException, - IOException, NoSuchAlgorithmException { - Tag nextTag = tagReader.readTag(); - - while ((nextTag != null) && (!nextTag.isEnd("stream"))) { - if (nextTag.isStart("error")) { - processStreamError(nextTag); - } else if (nextTag.isStart("features")) { - processStreamFeatures(nextTag); - } else if (nextTag.isStart("proceed")) { - switchOverToTls(nextTag); - } else if (nextTag.isStart("success")) { - final String challenge = tagReader.readElement(nextTag).getContent(); - try { - saslMechanism.getResponse(challenge); - } catch (final SaslMechanism.AuthenticationException e) { - disconnect(true); - Logging.e(Config.LOGTAG, String.valueOf(e)); - } - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": logged in"); - account.setKey(Account.PINNED_MECHANISM_KEY, - String.valueOf(saslMechanism.getPriority())); - tagReader.reset(); - sendStartStream(); - processStream(tagReader.readTag()); - break; - } else if (nextTag.isStart("failure")) { - throw new UnauthorizedException(); - } else if (nextTag.isStart("challenge")) { - final String challenge = tagReader.readElement(nextTag).getContent(); - final Element response = new Element("response"); - response.setAttribute("xmlns", - "urn:ietf:params:xml:ns:xmpp-sasl"); - try { - response.setContent(saslMechanism.getResponse(challenge)); - } catch (final SaslMechanism.AuthenticationException e) { - // TODO: Send auth abort tag. - Logging.e(Config.LOGTAG, e.toString()); - } - tagWriter.writeElement(response); - } else if (nextTag.isStart("enabled")) { - final Element enabled = tagReader.readElement(nextTag); - if ("true".equals(enabled.getAttribute("resume"))) { - this.streamId = enabled.getAttribute("id"); - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": stream managment(" + smVersion - + ") enabled (resumable)"); - } else { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": stream managment(" + smVersion + ") enabled"); - } - this.lastSessionStarted = SystemClock.elapsedRealtime(); - this.stanzasReceived = 0; - final RequestPacket r = new RequestPacket(smVersion); - tagWriter.writeStanzaAsync(r); - } else if (nextTag.isStart("resumed")) { - lastPacketReceived = SystemClock.elapsedRealtime(); - final Element resumed = tagReader.readElement(nextTag); - final String h = resumed.getAttribute("h"); - try { - final int serverCount = Integer.parseInt(h); - if (serverCount != stanzasSent) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": session resumed with lost packages"); - stanzasSent = serverCount; - } else { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": session resumed"); - } - if (acknowledgedListener != null) { - for (int i = 0; i < messageReceipts.size(); ++i) { - if (serverCount >= messageReceipts.keyAt(i)) { - acknowledgedListener.onMessageAcknowledged( - account, messageReceipts.valueAt(i)); - } - } - } - messageReceipts.clear(); - } catch (final NumberFormatException ignored) { - } - sendServiceDiscoveryInfo(account.getServer()); - sendServiceDiscoveryInfo(account.getJid().toBareJid()); - sendServiceDiscoveryItems(account.getServer()); - sendInitialPing(); - } else if (nextTag.isStart("r")) { - tagReader.readElement(nextTag); - if (Config.EXTENDED_SM_LOGGING) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": acknowledging stanza #" + this.stanzasReceived); - } - final AckPacket ack = new AckPacket(this.stanzasReceived, smVersion); - tagWriter.writeStanzaAsync(ack); - } else if (nextTag.isStart("a")) { - final Element ack = tagReader.readElement(nextTag); - lastPacketReceived = SystemClock.elapsedRealtime(); - try { - final int serverSequence = Integer.parseInt(ack.getAttribute("h")); - if (Config.EXTENDED_SM_LOGGING) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + serverSequence); - } - final String msgId = this.messageReceipts.get(serverSequence); - if (msgId != null) { - if (this.acknowledgedListener != null) { - this.acknowledgedListener.onMessageAcknowledged( - account, msgId); - } - this.messageReceipts.remove(serverSequence); - } - } catch (NumberFormatException e) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": server send ack without sequence number"); - } - } else if (nextTag.isStart("failed")) { - tagReader.readElement(nextTag); - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": resumption failed"); - streamId = null; - if (account.getStatus() != Account.State.ONLINE) { - sendBindRequest(); - } - } else if (nextTag.isStart("iq")) { - processIq(nextTag); - } else if (nextTag.isStart("message")) { - processMessage(nextTag); - } else if (nextTag.isStart("presence")) { - processPresence(nextTag); - } - nextTag = tagReader.readTag(); - } - if (account.getStatus() == Account.State.ONLINE) { - account. setStatus(Account.State.OFFLINE); - if (statusListener != null) { - statusListener.onStatusChanged(account); - } + private void processStream() throws XmlPullParserException, IOException, NoSuchAlgorithmException { + Tag nextTag = tagReader.readTag(); + while (nextTag != null && !nextTag.isEnd("stream")) { + if (nextTag.isStart("error")) { + processStreamError(nextTag); + } else if (nextTag.isStart("features")) { + processStreamFeatures(nextTag); + } else if (nextTag.isStart("proceed")) { + switchOverToTls(nextTag); + } else if (nextTag.isStart("success")) { + final String challenge = tagReader.readElement(nextTag).getContent(); + try { + saslMechanism.getResponse(challenge); + } catch (final SaslMechanism.AuthenticationException e) { + disconnect(true); + Log.e(Config.LOGTAG, String.valueOf(e)); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": logged in"); + account.setKey(Account.PINNED_MECHANISM_KEY, + String.valueOf(saslMechanism.getPriority())); + tagReader.reset(); + sendStartStream(); + final Tag tag = tagReader.readTag(); + if (tag != null && tag.isStart("stream")) { + processStream(); + } else { + throw new IOException("server didn't restart stream after successful auth"); + } + break; + } else if (nextTag.isStart("failure")) { + throw new UnauthorizedException(); + } else if (nextTag.isStart("challenge")) { + final String challenge = tagReader.readElement(nextTag).getContent(); + final Element response = new Element("response"); + response.setAttribute("xmlns", + "urn:ietf:params:xml:ns:xmpp-sasl"); + try { + response.setContent(saslMechanism.getResponse(challenge)); + } catch (final SaslMechanism.AuthenticationException e) { + // TODO: Send auth abort tag. + Log.e(Config.LOGTAG, e.toString()); + } + tagWriter.writeElement(response); + } else if (nextTag.isStart("enabled")) { + final Element enabled = tagReader.readElement(nextTag); + if ("true".equals(enabled.getAttribute("resume"))) { + this.streamId = enabled.getAttribute("id"); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": stream managment(" + smVersion + + ") enabled (resumable)"); + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": stream management(" + smVersion + ") enabled"); + } + this.stanzasReceived = 0; + final RequestPacket r = new RequestPacket(smVersion); + tagWriter.writeStanzaAsync(r); + } else if (nextTag.isStart("resumed")) { + lastPacketReceived = SystemClock.elapsedRealtime(); + final Element resumed = tagReader.readElement(nextTag); + final String h = resumed.getAttribute("h"); + try { + final int serverCount = Integer.parseInt(h); + if (serverCount != stanzasSent) { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + + ": session resumed with lost packages"); + stanzasSent = serverCount; + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": session resumed"); + } + acknowledgeStanzaUpTo(serverCount); + ArrayList<AbstractAcknowledgeableStanza> failedStanzas = new ArrayList<>(); + for(int i = 0; i < this.mStanzaQueue.size(); ++i) { + failedStanzas.add(mStanzaQueue.valueAt(i)); + } + mStanzaQueue.clear(); + Log.d(Config.LOGTAG,"resending "+failedStanzas.size()+" stanzas"); + for(AbstractAcknowledgeableStanza packet : failedStanzas) { + if (packet instanceof MessagePacket) { + MessagePacket message = (MessagePacket) packet; + mXmppConnectionService.markMessage(account, + message.getTo().toBareJid(), + message.getId(), + Message.STATUS_UNSEND); } + sendPacket(packet); + } + } catch (final NumberFormatException ignored) { + } + Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": online with resource " + account.getResource()); + changeStatus(Account.State.ONLINE); + } else if (nextTag.isStart("r")) { + tagReader.readElement(nextTag); + if (Config.EXTENDED_SM_LOGGING) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": acknowledging stanza #" + this.stanzasReceived); + } + final AckPacket ack = new AckPacket(this.stanzasReceived, smVersion); + tagWriter.writeStanzaAsync(ack); + } else if (nextTag.isStart("a")) { + final Element ack = tagReader.readElement(nextTag); + lastPacketReceived = SystemClock.elapsedRealtime(); + try { + final int serverSequence = Integer.parseInt(ack.getAttribute("h")); + acknowledgeStanzaUpTo(serverSequence); + } catch (NumberFormatException e) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server send ack without sequence number"); + } + } else if (nextTag.isStart("failed")) { + tagReader.readElement(nextTag); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": resumption failed"); + resetStreamId(); + if (account.getStatus() != Account.State.ONLINE) { + sendBindRequest(); + } + } else if (nextTag.isStart("iq")) { + processIq(nextTag); + } else if (nextTag.isStart("message")) { + processMessage(nextTag); + } else if (nextTag.isStart("presence")) { + processPresence(nextTag); + } + nextTag = tagReader.readTag(); + } + throw new IOException("reached end of stream. last tag was "+nextTag); } - private void sendInitialPing() { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": sending intial ping"); - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); - iq.setFrom(account.getJid()); - iq.addChild("ping", "urn:xmpp:ping"); - this.sendIqPacket(iq, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() - + ": online with resource " + account.getResource()); - changeStatus(Account.State.ONLINE); + private void acknowledgeStanzaUpTo(int serverCount) { + for (int i = 0; i < mStanzaQueue.size(); ++i) { + if (serverCount >= mStanzaQueue.keyAt(i)) { + if (Config.EXTENDED_SM_LOGGING) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + mStanzaQueue.keyAt(i)); + } + AbstractAcknowledgeableStanza stanza = mStanzaQueue.valueAt(i); + if (stanza instanceof MessagePacket && acknowledgedListener != null) { + MessagePacket packet = (MessagePacket) stanza; + acknowledgedListener.onMessageAcknowledged(account, packet.getId()); + } + mStanzaQueue.removeAt(i); + i--; } - }); + } } private Element processPacket(final Tag currentTag, final int packetType) @@ -446,26 +601,32 @@ public class XmppConnection implements Runnable { this.jingleListener.onJinglePacketReceived(account,(JinglePacket) packet); } } else { - if (packetCallbacks.containsKey(packet.getId())) { - final Pair<IqPacket, OnIqPacketReceived> packetCallbackDuple = packetCallbacks.get(packet.getId()); - // Packets to the server should have responses from the server - if (packetCallbackDuple.first.toServer(account)) { - if (packet.fromServer(account)) { - packetCallbackDuple.second.onIqPacketReceived(account, packet); - packetCallbacks.remove(packet.getId()); - } else { - Logging.e(Config.LOGTAG,account.getJid().toBareJid().toString()+": ignoring spoofed iq packet"); - } - } else { - if (packet.getFrom().equals(packetCallbackDuple.first.getTo())) { - packetCallbackDuple.second.onIqPacketReceived(account, packet); - packetCallbacks.remove(packet.getId()); + OnIqPacketReceived callback = null; + synchronized (this.packetCallbacks) { + if (packetCallbacks.containsKey(packet.getId())) { + final Pair<IqPacket, OnIqPacketReceived> packetCallbackDuple = packetCallbacks.get(packet.getId()); + // Packets to the server should have responses from the server + if (packetCallbackDuple.first.toServer(account)) { + if (packet.fromServer(account) || mServerIdentity == Identity.FACEBOOK) { + callback = packetCallbackDuple.second; + packetCallbacks.remove(packet.getId()); + } else { + Log.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet"); + } } else { - Logging.e(Config.LOGTAG,account.getJid().toBareJid().toString()+": ignoring spoofed iq packet"); + if (packet.getFrom().equals(packetCallbackDuple.first.getTo())) { + callback = packetCallbackDuple.second; + packetCallbacks.remove(packet.getId()); + } else { + Logging.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet"); + } } + } else if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { + callback = this.unregisteredIqListener; } - } else if (packet.getType() == IqPacket.TYPE.GET|| packet.getType() == IqPacket.TYPE.SET) { - this.unregisteredIqListener.onIqPacketReceived(account, packet); + } + if (callback != null) { + callback.onIqPacketReceived(account,packet); } } } @@ -486,53 +647,44 @@ public class XmppConnection implements Runnable { tagWriter.writeTag(startTLS); } + + private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException { tagReader.readTag(); try { - final SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null,new X509TrustManager[]{this.mXmppConnectionService.getMemorizingTrustManager()},mXmppConnectionService.getRNG()); - final SSLSocketFactory factory = sc.getSocketFactory(); - final HostnameVerifier verifier = this.mXmppConnectionService.getMemorizingTrustManager().wrapHostnameVerifier(new StrictHostnameVerifier()); + final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); final InetAddress address = socket == null ? null : socket.getInetAddress(); - if (factory == null || address == null || verifier == null) { + if (address == null) { throw new IOException("could not setup ssl"); } - final SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,address.getHostAddress(), socket.getPort(),true); + final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true); if (sslSocket == null) { throw new IOException("could not initialize ssl socket"); } - final String[] supportProtocols; - final Collection<String> supportedProtocols = new LinkedList<>( - Arrays.asList(sslSocket.getSupportedProtocols())); - supportedProtocols.remove("SSLv3"); - supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]); - - sslSocket.setEnabledProtocols(supportProtocols); - - final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( - sslSocket.getSupportedCipherSuites()); - //Logging.d(Config.LOGTAG, "Using ciphers: " + Arrays.toString(cipherSuites)); - if (cipherSuites.length > 0) { - sslSocket.setEnabledCipherSuites(cipherSuites); - } + SSLSocketHelper.setSecurity(sslSocket); - if (!verifier.verify(account.getServer().getDomainpart(),sslSocket.getSession())) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); + if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), sslSocket.getSession())) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); throw new SecurityException(); } tagReader.setInputStream(sslSocket.getInputStream()); tagWriter.setOutputStream(sslSocket.getOutputStream()); sendStartStream(); - Logging.d(Config.LOGTAG, account.getJid().toBareJid()+ ": TLS connection established"); + Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": TLS connection established"); features.encryptionEnabled = true; - processStream(tagReader.readTag()); + final Tag tag = tagReader.readTag(); + if (tag != null && tag.isStart("stream")) { + processStream(); + } else { + throw new IOException("server didn't restart stream after STARTTLS"); + } sslSocket.close(); } catch (final NoSuchAlgorithmException | KeyManagementException e1) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed"); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); throw new SecurityException(); } } @@ -542,21 +694,26 @@ public class XmppConnection implements Runnable { this.streamFeatures = tagReader.readElement(currentTag); if (this.streamFeatures.hasChild("starttls") && !features.encryptionEnabled) { sendStartTLS(); - } else if (this.streamFeatures.hasChild("register") - && account.isOptionSet(Account.OPTION_REGISTER) - && features.encryptionEnabled) { - sendRegistryRequest(); + } else if (this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) { + if (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS) { + sendRegistryRequest(); + } else { + throw new IncompatibleServerException(); + } } else if (!this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) { changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED); disconnect(true); } else if (this.streamFeatures.hasChild("mechanisms") - && shouldAuthenticate && features.encryptionEnabled) { + && shouldAuthenticate + && (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS)) { final List<String> mechanisms = extractMechanisms(streamFeatures .findChild("mechanisms")); final Element auth = new Element("auth"); auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); - if (mechanisms.contains("SCRAM-SHA-1")) { + if (mechanisms.contains("EXTERNAL") && account.getPrivateKeyAlias() != null) { + saslMechanism = new External(tagWriter, account, mXmppConnectionService.getRNG()); + } else if (mechanisms.contains("SCRAM-SHA-1")) { saslMechanism = new ScramSha1(tagWriter, account, mXmppConnectionService.getRNG()); } else if (mechanisms.contains("PLAIN")) { saslMechanism = new Plain(tagWriter, account); @@ -586,19 +743,18 @@ public class XmppConnection implements Runnable { } else { throw new IncompatibleServerException(); } - } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" - + smVersion) - && streamId != null) { + } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" + smVersion) && streamId != null) { if (Config.EXTENDED_SM_LOGGING) { Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived); } final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion); this.tagWriter.writeStanzaAsync(resume); - } else if (this.streamFeatures.hasChild("bind") && shouldBind) { - sendBindRequest(); - } else { - disconnect(true); - changeStatus(Account.State.INCOMPATIBLE_SERVER); + } else if (needsBinding) { + if (this.streamFeatures.hasChild("bind")) { + sendBindRequest(); + } else { + throw new IncompatibleServerException(); + } } } @@ -611,6 +767,15 @@ public class XmppConnection implements Runnable { return mechanisms; } + public void sendCaptchaRegistryRequest(String id, Data data) { + if (data == null) { + setAccountCreationFailed(""); + } else { + IqPacket request = getIqGenerator().generateCreateAccountWithCaptcha(account, id, data); + sendIqPacket(request, createPacketReceiveHandler()); + } + } + private void sendRegistryRequest() { final IqPacket register = new IqPacket(IqPacket.TYPE.GET); register.query("jabber:iq:register"); @@ -619,59 +784,95 @@ public class XmppConnection implements Runnable { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - final Element instructions = packet.query().findChild("instructions"); - if (packet.query().hasChild("username") + boolean failed = false; + if (packet.getType() == IqPacket.TYPE.RESULT + && packet.query().hasChild("username") && (packet.query().hasChild("password"))) { final IqPacket register = new IqPacket(IqPacket.TYPE.SET); final Element username = new Element("username").setContent(account.getUsername()); final Element password = new Element("password").setContent(account.getPassword()); register.query("jabber:iq:register").addChild(username); register.query().addChild(password); - sendIqPacket(register, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - account.setOption(Account.OPTION_REGISTER, - false); - changeStatus(Account.State.REGISTRATION_SUCCESSFUL); - } else if (packet.hasChild("error") - && (packet.findChild("error") - .hasChild("conflict"))) { - changeStatus(Account.State.REGISTRATION_CONFLICT); - } else { - changeStatus(Account.State.REGISTRATION_FAILED); - Logging.d(Config.LOGTAG, packet.toString()); - } - disconnect(true); + sendIqPacket(register, createPacketReceiveHandler()); + } else if (packet.getType() == IqPacket.TYPE.RESULT + && (packet.query().hasChild("x", "jabber:x:data"))) { + final Data data = Data.parse(packet.query().findChild("x", "jabber:x:data")); + final Element blob = packet.query().findChild("data", "urn:xmpp:bob"); + final String id = packet.getId(); + + Bitmap captcha = null; + if (blob != null) { + try { + final String base64Blob = blob.getContent(); + final byte[] strBlob = Base64.decode(base64Blob, Base64.DEFAULT); + InputStream stream = new ByteArrayInputStream(strBlob); + captcha = BitmapFactory.decodeStream(stream); + } catch (Exception e) { + //ignored } - }); + } else { + try { + Field url = data.getFieldByName("url"); + String urlString = url.findChildContent("value"); + URL uri = new URL(urlString); + captcha = BitmapFactory.decodeStream(uri.openConnection().getInputStream()); + } catch (IOException e) { + Logging.e(Config.LOGTAG, e.toString()); + } + } + + if (captcha != null) { + failed = !mXmppConnectionService.displayCaptchaRequest(account, id, data, captcha); + } } else { - changeStatus(Account.State.REGISTRATION_FAILED); - disconnect(true); - Logging.d(Config.LOGTAG, account.getJid().toBareJid() - + ": could not register. instructions are" - + (instructions != null ? instructions.getContent() : "")); + failed = true; + } + + if (failed) { + final Element instructions = packet.query().findChild("instructions"); + setAccountCreationFailed((instructions != null) ? instructions.getContent() : ""); } } }); } + private void setAccountCreationFailed(String instructions) { + changeStatus(Account.State.REGISTRATION_FAILED); + disconnect(true); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": could not register. instructions are" + + instructions); + } + + public void resetEverything() { + resetStreamId(); + clearIqCallbacks(); + mStanzaQueue.clear(); + synchronized (this.disco) { + disco.clear(); + } + } + private void sendBindRequest() { - while(!mXmppConnectionService.areMessagesInitialized()) { + while(!mXmppConnectionService.areMessagesInitialized() && socket != null && !socket.isClosed()) { try { Thread.sleep(500); } catch (final InterruptedException ignored) { } } + needsBinding = false; + clearIqCallbacks(); final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); iq.addChild("bind", "urn:ietf:params:xml:ns:xmpp-bind") .addChild("resource").setContent(account.getResource()); this.sendUnmodifiedIqPacket(iq, new OnIqPacketReceived() { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + return; + } final Element bind = packet.findChild("bind"); - if (bind != null) { + if (bind != null && packet.getType() == IqPacket.TYPE.RESULT) { final Element jid = bind.findChild("jid"); if (jid != null && jid.getContent() != null) { try { @@ -685,24 +886,71 @@ public class XmppConnection implements Runnable { sendPostBindInitialization(); } } else { + Logging.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure"); disconnect(true); } } else { + Logging.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure"); disconnect(true); } } }); } + private void clearIqCallbacks() { + final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.TIMEOUT); + final ArrayList<OnIqPacketReceived> callbacks = new ArrayList<>(); + synchronized (this.packetCallbacks) { + if (this.packetCallbacks.size() == 0) { + return; + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": clearing "+this.packetCallbacks.size()+" iq callbacks"); + final Iterator<Pair<IqPacket, OnIqPacketReceived>> iterator = this.packetCallbacks.values().iterator(); + while (iterator.hasNext()) { + Pair<IqPacket, OnIqPacketReceived> entry = iterator.next(); + callbacks.add(entry.second); + iterator.remove(); + } + } + for(OnIqPacketReceived callback : callbacks) { + callback.onIqPacketReceived(account,failurePacket); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": done clearing iq callbacks. " + this.packetCallbacks.size() + " left"); + } + + public void sendDiscoTimeout() { + final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.ERROR); //don't use timeout + final ArrayList<OnIqPacketReceived> callbacks = new ArrayList<>(); + synchronized (this.mPendingServiceDiscoveriesIds) { + for(String id : mPendingServiceDiscoveriesIds) { + synchronized (this.packetCallbacks) { + Pair<IqPacket, OnIqPacketReceived> pair = this.packetCallbacks.remove(id); + if (pair != null) { + callbacks.add(pair.second); + } + } + } + this.mPendingServiceDiscoveriesIds.clear(); + } + if (callbacks.size() > 0) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": sending disco timeout"); + resetStreamId(); //we don't want to live with this for ever + } + for(OnIqPacketReceived callback : callbacks) { + callback.onIqPacketReceived(account,failurePacket); + } + } + private void sendStartSession() { final IqPacket startSession = new IqPacket(IqPacket.TYPE.SET); - startSession.addChild("session","urn:ietf:params:xml:ns:xmpp-session"); + startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session"); this.sendUnmodifiedIqPacket(startSession, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { if (packet.getType() == IqPacket.TYPE.RESULT) { sendPostBindInitialization(); - } else { + } else if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not init sessions"); disconnect(true); } } @@ -720,56 +968,96 @@ public class XmppConnection implements Runnable { final EnablePacket enable = new EnablePacket(smVersion); tagWriter.writeStanzaAsync(enable); stanzasSent = 0; - messageReceipts.clear(); + mStanzaQueue.clear(); } features.carbonsEnabled = false; features.blockListRequested = false; - disco.clear(); - sendServiceDiscoveryInfo(account.getServer()); - sendServiceDiscoveryInfo(account.getJid().toBareJid()); + synchronized (this.disco) { + this.disco.clear(); + } + mPendingServiceDiscoveries = mServerIdentity == Identity.NIMBUZZ ? 1 : 0; + lastDiscoStarted = SystemClock.elapsedRealtime(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": starting service discovery"); + mXmppConnectionService.scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode()); sendServiceDiscoveryItems(account.getServer()); - if (bindListener != null) { - bindListener.onBind(account); + Element caps = streamFeatures.findChild("c"); + final String hash = caps == null ? null : caps.getAttribute("hash"); + final String ver = caps == null ? null : caps.getAttribute("ver"); + ServiceDiscoveryResult discoveryResult = null; + if (hash != null && ver != null) { + discoveryResult = mXmppConnectionService.databaseBackend.findDiscoveryResult(hash, ver); } - sendInitialPing(); + if (discoveryResult == null) { + sendServiceDiscoveryInfo(account.getServer()); + } else { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": server caps came from cache"); + disco.put(account.getServer(), discoveryResult); + } + sendServiceDiscoveryInfo(account.getJid().toBareJid()); + this.lastSessionStarted = SystemClock.elapsedRealtime(); } private void sendServiceDiscoveryInfo(final Jid jid) { - if (disco.containsKey(jid)) { - if (account.getServer().equals(jid)) { - enableAdvancedStreamFeatures(); - } - } else { - final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); - iq.setTo(jid); - iq.query("http://jabber.org/protocol/disco#info"); - this.sendIqPacket(iq, new OnIqPacketReceived() { + if (mServerIdentity != Identity.NIMBUZZ) { + mPendingServiceDiscoveries++; + } + final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); + iq.setTo(jid); + iq.query("http://jabber.org/protocol/disco#info"); + String id = this.sendIqPacket(iq, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(final Account account, final IqPacket packet) { - final List<Element> elements = packet.query().getChildren(); - final Info info = new Info(); - for (final Element element : elements) { - if (element.getName().equals("identity")) { - String type = element.getAttribute("type"); - String category = element.getAttribute("category"); - if (type != null && category != null) { - info.identities.add(new Pair<>(category,type)); - } - } else if (element.getName().equals("feature")) { - info.features.add(element.getAttribute("var")); + @Override + public void onIqPacketReceived(final Account account, final IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + boolean advancedStreamFeaturesLoaded; + synchronized (XmppConnection.this.disco) { + ServiceDiscoveryResult result = new ServiceDiscoveryResult(packet); + for (final ServiceDiscoveryResult.Identity id : result.getIdentities()) { + if (mServerIdentity == Identity.UNKNOWN && id.getType().equals("im") && + id.getCategory().equals("server") && id.getName() != null && + jid.equals(account.getServer())) { + switch (id.getName()) { + case "Prosody": + mServerIdentity = Identity.PROSODY; + break; + case "ejabberd": + mServerIdentity = Identity.EJABBERD; + break; + case "Slack-XMPP": + mServerIdentity = Identity.SLACK; + break; + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server name: " + id.getName()); + } + } + if (jid.equals(account.getServer())) { + mXmppConnectionService.databaseBackend.insertDiscoveryResult(result); } + disco.put(jid, result); + advancedStreamFeaturesLoaded = disco.containsKey(account.getServer()) + && disco.containsKey(account.getJid().toBareJid()); } - disco.put(jid, info); - - if (account.getServer().equals(jid)) { + if (advancedStreamFeaturesLoaded && (jid.equals(account.getServer()) || jid.equals(account.getJid().toBareJid()))) { enableAdvancedStreamFeatures(); - for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) { - listener.onAdvancedStreamFeaturesAvailable(account); + } + } else { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString()); + } + if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + mPendingServiceDiscoveries--; + if (mPendingServiceDiscoveries == 0) { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": done with service discovery"); + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": online with resource " + account.getResource()); + if (bindListener != null) { + bindListener.onBind(account); } + changeStatus(Account.State.ONLINE); } } - }); + } + }); + synchronized (this.mPendingServiceDiscoveriesIds) { + this.mPendingServiceDiscoveriesIds.add(id); } } @@ -778,9 +1066,12 @@ public class XmppConnection implements Runnable { sendEnableCarbons(); } if (getFeatures().blocking() && !features.blockListRequested) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": Requesting block list"); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Requesting block list"); this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser()); } + for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) { + listener.onAdvancedStreamFeaturesAvailable(account); + } } private void sendServiceDiscoveryItems(final Jid server) { @@ -791,14 +1082,18 @@ public class XmppConnection implements Runnable { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - final List<Element> elements = packet.query().getChildren(); - for (final Element element : elements) { - if (element.getName().equals("item")) { - final Jid jid = element.getAttributeAsJid("jid"); - if (jid != null && !jid.equals(account.getServer())) { - sendServiceDiscoveryInfo(jid); + if (packet.getType() == IqPacket.TYPE.RESULT) { + final List<Element> elements = packet.query().getChildren(); + for (final Element element : elements) { + if (element.getName().equals("item")) { + final Jid jid = element.getAttributeAsJid("jid"); + if (jid != null && !jid.equals(account.getServer())) { + sendServiceDiscoveryInfo(jid); + } } } + } else { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco items of " + server); } } }); @@ -832,12 +1127,13 @@ public class XmppConnection implements Runnable { Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": switching resource due to conflict (" + account.getResource() + ")"); + } else if (streamError != null) { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": stream error "+streamError.toString()); } } private void sendStartStream() throws IOException { final Tag stream = Tag.start("stream:stream"); - stream.setAttribute("from", account.getJid().toBareJid().toString()); stream.setAttribute("to", account.getServer().toString()); stream.setAttribute("version", "1.0"); stream.setAttribute("xml:lang", "en"); @@ -850,24 +1146,23 @@ public class XmppConnection implements Runnable { return new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); } - public void sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { + public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { packet.setFrom(account.getJid()); - this.sendUnmodifiedIqPacket(packet,callback); - + return this.sendUnmodifiedIqPacket(packet, callback); } - private synchronized void sendUnmodifiedIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { + private synchronized String sendUnmodifiedIqPacket(final IqPacket packet, final OnIqPacketReceived callback) { if (packet.getId() == null) { final String id = nextRandomId(); packet.setAttribute("id", id); } if (callback != null) { - if (packet.getId() == null) { - packet.setId(nextRandomId()); + synchronized (this.packetCallbacks) { + packetCallbacks.put(packet.getId(), new Pair<>(packet, callback)); } - packetCallbacks.put(packet.getId(), new Pair<>(packet, callback)); } this.sendPacket(packet); + return packet.getId(); } public void sendMessagePacket(final MessagePacket packet) { @@ -884,24 +1179,22 @@ public class XmppConnection implements Runnable { disconnect(true); return; } - final String name = packet.getName(); - if (name.equals("iq") || name.equals("message") || name.equals("presence")) { - ++stanzasSent; - } tagWriter.writeStanzaAsync(packet); - if (packet instanceof MessagePacket && packet.getId() != null && getFeatures().sm()) { - if (Config.EXTENDED_SM_LOGGING) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for message stanza #" + stanzasSent); + if (packet instanceof AbstractAcknowledgeableStanza) { + AbstractAcknowledgeableStanza stanza = (AbstractAcknowledgeableStanza) packet; + ++stanzasSent; + this.mStanzaQueue.put(stanzasSent, stanza); + if (stanza instanceof MessagePacket && stanza.getId() != null && getFeatures().sm()) { + if (Config.EXTENDED_SM_LOGGING) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for message stanza #" + stanzasSent); + } + tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion)); } - this.messageReceipts.put(stanzasSent, packet.getId()); - tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion)); } } public void sendPing() { - if (streamFeatures.hasChild("sm")) { - tagWriter.writeStanzaAsync(new RequestPacket(smVersion)); - } else { + if (!r()) { final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); iq.setFrom(account.getJid()); iq.addChild("ping", "urn:xmpp:ping"); @@ -948,37 +1241,71 @@ public class XmppConnection implements Runnable { } } - public void disconnect(final boolean force) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting"); - try { - if (force) { - socket.close(); - return; - } + public void waitForPush() { + if (tagWriter.isActive()) { + tagWriter.finish(); new Thread(new Runnable() { - @Override public void run() { - if (tagWriter.isActive()) { - tagWriter.finish(); - try { - while (!tagWriter.finished()) { - Logging.d(Config.LOGTAG, "not yet finished"); - Thread.sleep(100); - } - tagWriter.writeTag(Tag.end("stream:stream")); - socket.close(); - } catch (final IOException e) { - Logging.d(Config.LOGTAG, - "io exception during disconnect"); - } catch (final InterruptedException e) { - Logging.d(Config.LOGTAG, "interrupted"); + try { + while(!tagWriter.finished()) { + Thread.sleep(10); } + socket.close(); + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream"); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); } } }).start(); - } catch (final IOException e) { - Logging.d(Config.LOGTAG, "io exception during disconnect"); + } else { + forceCloseSocket(); + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)"); + } + } + + private void forceCloseSocket() { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + Logging.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": exception during force close ("+e.getMessage()+")"); + } + } + } + + public void disconnect(final boolean force) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting force="+Boolean.valueOf(force)); + if (force) { + forceCloseSocket(); + return; + } else { + if (tagWriter.isActive()) { + tagWriter.finish(); + try { + int i = 0; + boolean warned = false; + while (!tagWriter.finished() && socket.isConnected() && i <= 10) { + if (!warned) { + Log.d(Config.LOGTAG, account.getJid().toBareJid()+": waiting for tag writer to finish"); + warned = true; + } + Thread.sleep(200); + i++; + } + if (warned) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": tag writer has finished"); + } + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closing stream"); + tagWriter.writeTag(Tag.end("stream:stream")); + } catch (final IOException e) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": io exception during disconnect ("+e.getMessage()+")"); + } catch (final InterruptedException e) { + Log.d(Config.LOGTAG, "interrupted"); + } + } } } @@ -987,13 +1314,15 @@ public class XmppConnection implements Runnable { } public List<Jid> findDiscoItemsByFeature(final String feature) { - final List<Jid> items = new ArrayList<>(); - for (final Entry<Jid, Info> cursor : disco.entrySet()) { - if (cursor.getValue().features.contains(feature)) { - items.add(cursor.getKey()); + synchronized (this.disco) { + final List<Jid> items = new ArrayList<>(); + for (final Entry<Jid, ServiceDiscoveryResult> cursor : this.disco.entrySet()) { + if (cursor.getValue().getFeatures().contains(feature)) { + items.add(cursor.getKey()); + } } + return items; } - return items; } public Jid findDiscoItemByFeature(final String feature) { @@ -1004,17 +1333,24 @@ public class XmppConnection implements Runnable { return null; } - public void r() { - this.tagWriter.writeStanzaAsync(new RequestPacket(smVersion)); + public boolean r() { + if (getFeatures().sm()) { + this.tagWriter.writeStanzaAsync(new RequestPacket(smVersion)); + return true; + } else { + return false; + } } public String getMucServer() { - for (final Entry<Jid, Info> cursor : disco.entrySet()) { - final Info value = cursor.getValue(); - if (value.features.contains("http://jabber.org/protocol/muc") - && !value.features.contains("jabber:iq:gateway") - && !value.identities.contains(new Pair<>("conference","irc"))) { - return cursor.getKey().toString(); + synchronized (this.disco) { + for (final Entry<Jid, ServiceDiscoveryResult> cursor : disco.entrySet()) { + final ServiceDiscoveryResult value = cursor.getValue(); + if (value.getFeatures().contains("http://jabber.org/protocol/muc") + && !value.getFeatures().contains("jabber:iq:gateway") + && !value.hasIdentity("conference", "irc")) { + return cursor.getKey().toString(); + } } } return null; @@ -1052,6 +1388,9 @@ public class XmppConnection implements Runnable { return this.lastPingSent; } + public long getLastDiscoStarted() { + return this.lastDiscoStarted; + } public long getLastPacketReceived() { return this.lastPacketReceived; } @@ -1069,9 +1408,12 @@ public class XmppConnection implements Runnable { this.lastConnect = 0; } - private class Info { - public final ArrayList<String> features = new ArrayList<>(); - public final ArrayList<Pair<String,String>> identities = new ArrayList<>(); + public void setInteractive(boolean interactive) { + this.mInteractive = interactive; + } + + public Identity getServerIdentity() { + return mServerIdentity; } private class UnauthorizedException extends IOException { @@ -1086,6 +1428,15 @@ public class XmppConnection implements Runnable { } + public enum Identity { + FACEBOOK, + SLACK, + EJABBERD, + PROSODY, + NIMBUZZ, + UNKNOWN + } + public class Features { XmppConnection connection; private boolean carbonsEnabled = false; @@ -1097,8 +1448,10 @@ public class XmppConnection implements Runnable { } private boolean hasDiscoFeature(final Jid server, final String feature) { - return connection.disco.containsKey(server) && - connection.disco.get(server).features.contains(feature); + synchronized (XmppConnection.this.disco) { + return connection.disco.containsKey(server) && + connection.disco.get(server).getFeatures().contains(feature); + } } public boolean carbons() { @@ -1123,26 +1476,26 @@ public class XmppConnection implements Runnable { } public boolean pep() { - final Pair<String,String> needle = new Pair<>("pubsub","pep"); - Info info = disco.get(account.getServer()); - if (info != null && info.identities.contains(needle)) { - return true; - } else { - info = disco.get(account.getJid().toBareJid()); - return info != null && info.identities.contains(needle); + synchronized (XmppConnection.this.disco) { + final Pair<String, String> needle = new Pair<>("pubsub", "pep"); + ServiceDiscoveryResult info = disco.get(account.getServer()); + if (info != null && info.hasIdentity("pubsub", "pep")) { + return true; + } else { + info = disco.get(account.getJid().toBareJid()); + return info != null && info.hasIdentity("pubsub", "pep"); + } } } public boolean mam() { - if (hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:mam:0")) { - return true; - } else { - return hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0"); - } + return hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:mam:0") + || hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0"); } - public boolean advancedStreamFeaturesLoaded() { - return disco.containsKey(account.getServer()); + public boolean push() { + return hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:push:0") + || hasDiscoFeature(account.getServer(), "urn:xmpp:push:0"); } public boolean rosterVersioning() { @@ -1154,7 +1507,7 @@ public class XmppConnection implements Runnable { } public boolean httpUpload() { - return findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD).size() > 0; + return !Config.DISABLE_HTTP_UPLOAD && findDiscoItemsByFeature(Xmlns.HTTP_UPLOAD).size() > 0; } } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketGenerator.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketGenerator.java index bb7e557e..1574604e 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketGenerator.java @@ -1,9 +1,9 @@ package de.thedevstack.conversationsplus.xmpp.avatar; +import de.thedevstack.conversationsplus.xmpp.pubsub.PubSubPacketGenerator; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.xmpp.pep.Avatar; -import de.thedevstack.conversationsplus.xmpp.pubsub.PubSubPacketGenerator; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; /** diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketParser.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketParser.java index 26d5202f..0867524b 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketParser.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketParser.java @@ -1,7 +1,7 @@ package de.thedevstack.conversationsplus.xmpp.avatar; -import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.pubsub.PubSubPacketParser; +import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; /** diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/chatstate/ChatState.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/chatstate/ChatState.java index 929d485a..323d99c2 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/chatstate/ChatState.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/chatstate/ChatState.java @@ -4,7 +4,7 @@ import de.thedevstack.conversationsplus.xml.Element; public enum ChatState { - ACTIVE, INACTIVE, GONE, COMPOSING, PAUSED, mIncomingChatState; + ACTIVE, INACTIVE, GONE, COMPOSING, PAUSED; public static ChatState parse(Element element) { final String NAMESPACE = "http://jabber.org/protocol/chatstates"; diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Data.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Data.java index 591d8ada..cb612a07 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Data.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Data.java @@ -53,16 +53,16 @@ public class Data extends Element { public void submit() { this.setAttribute("type","submit"); - removeNonFieldChildren(); + removeUnnecessaryChildren(); for(Field field : getFields()) { field.removeNonValueChildren(); } } - private void removeNonFieldChildren() { + private void removeUnnecessaryChildren() { for(Iterator<Element> iterator = this.children.iterator(); iterator.hasNext();) { Element element = iterator.next(); - if (!element.getName().equals("field")) { + if (!element.getName().equals("field") && !element.getName().equals("title")) { iterator.remove(); } } @@ -76,10 +76,20 @@ public class Data extends Element { } public void setFormType(String formType) { - this.put("FORM_TYPE",formType); + this.put("FORM_TYPE", formType); } public String getFormType() { - return this.getAttribute("FORM_TYPE"); + String type = getValue("FORM_TYPE"); + return type == null ? "" : type; + } + + public String getValue(String name) { + Field field = this.getFieldByName(name); + return field == null ? null : field.getValue(); + } + + public String getTitle() { + return findChildContent("title"); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Field.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Field.java index 9eb2d08d..88b3155c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Field.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/forms/Field.java @@ -1,7 +1,9 @@ package de.thedevstack.conversationsplus.xmpp.forms; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.List; import de.thedevstack.conversationsplus.xml.Element; @@ -16,7 +18,7 @@ public class Field extends Element { super("field"); } - public String getName() { + public String getFieldName() { return this.getAttribute("var"); } @@ -47,4 +49,33 @@ public class Field extends Element { field.setChildren(element.getChildren()); return field; } + + public String getValue() { + return findChildContent("value"); + } + + public List<String> getValues() { + List<String> values = new ArrayList<>(); + for(Element child : getChildren()) { + if ("value".equals(child.getName())) { + String content = child.getContent(); + if (content != null) { + values.add(content); + } + } + } + return values; + } + + public String getLabel() { + return getAttribute("label"); + } + + public String getType() { + return getAttribute("type"); + } + + public boolean isRequired() { + return hasChild("required"); + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jid/Jid.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jid/Jid.java index 5aaf1563..bb32e821 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jid/Jid.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jid/Jid.java @@ -176,7 +176,7 @@ public final class Jid { return resourcepart.isEmpty() ? this : fromParts(localpart, domainpart, ""); } catch (final InvalidJidException e) { // This should never happen. - return null; + throw new AssertionError("Jid " + this.toString() + " invalid"); } } @@ -185,7 +185,7 @@ public final class Jid { return resourcepart.isEmpty() && localpart.isEmpty() ? this : fromString(getDomainpart()); } catch (final InvalidJidException e) { // This should never happen. - return null; + throw new AssertionError("Jid " + this.toString() + " invalid"); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java index 7885cc0f..c4ceeb4e 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java @@ -1,5 +1,12 @@ package de.thedevstack.conversationsplus.xmpp.jingle; +import android.content.Intent; +import android.net.Uri; +import android.util.Pair; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -8,22 +15,23 @@ import java.util.Locale; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; -import android.content.Intent; -import android.net.Uri; -import android.os.SystemClock; - import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.utils.StreamUtil; +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; +import de.thedevstack.conversationsplus.crypto.axolotl.OnMessageCreatedCallback; +import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlMessage; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; -import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.entities.DownloadableFile; -import de.thedevstack.conversationsplus.entities.TransferablePlaceholder; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.Transferable; +import de.thedevstack.conversationsplus.entities.TransferablePlaceholder; import de.thedevstack.conversationsplus.persistance.FileBackend; +import de.thedevstack.conversationsplus.services.AbstractConnectionManager; import de.thedevstack.conversationsplus.services.XmppConnectionService; -import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; import de.thedevstack.conversationsplus.xmpp.jid.Jid; @@ -63,20 +71,24 @@ public class JingleConnection implements Transferable { private String contentCreator; private int mProgress = 0; - private long mLastGuiRefresh = 0; private boolean receivedCandidate = false; private boolean sentCandidate = false; private boolean acceptedAutomatically = false; + private XmppAxolotlMessage mXmppAxolotlMessage; + private JingleTransport transport = null; + private OutputStream mFileOutputStream; + private InputStream mFileInputStream; + private OnIqPacketReceived responseListener = new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.ERROR) { + if (packet.getType() != IqPacket.TYPE.RESULT) { fail(); } } @@ -88,15 +100,13 @@ public class JingleConnection implements Transferable { public void onFileTransmitted(DownloadableFile file) { if (responder.equals(account.getJid())) { sendSuccess(); + MessageUtil.updateFileParams(message); + mXmppConnectionService.databaseBackend.createMessage(message); + mXmppConnectionService.markMessage(message,Message.STATUS_RECEIVED); if (acceptedAutomatically) { message.markUnread(); - JingleConnection.this.mXmppConnectionService - .getNotificationService().push(message); + JingleConnection.this.mXmppConnectionService.getNotificationService().push(message); } - MessageUtil.updateFileParams(message); - mXmppConnectionService.databaseBackend.createMessage(message); - mXmppConnectionService.markMessage(message, - Message.STATUS_RECEIVED); } else { if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { file.delete(); @@ -107,6 +117,8 @@ public class JingleConnection implements Transferable { Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(Uri.fromFile(file)); mXmppConnectionService.sendBroadcast(intent); + } else { + account.getPgpDecryptionService().add(message); } } @@ -117,6 +129,14 @@ public class JingleConnection implements Transferable { } }; + public InputStream getFileInputStream() { + return this.mFileInputStream; + } + + public OutputStream getFileOutputStream() { + return this.mFileOutputStream; + } + private OnProxyActivated onProxyActivated = new OnProxyActivated() { @Override @@ -198,7 +218,26 @@ public class JingleConnection implements Transferable { mXmppConnectionService.sendIqPacket(account,response,null); } - public void init(Message message) { + public void init(final Message message) { + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + Conversation conversation = message.getConversation(); + conversation.getAccount().getAxolotlService().prepareKeyTransportMessage(conversation.getContact(), new OnMessageCreatedCallback() { + @Override + public void run(XmppAxolotlMessage xmppAxolotlMessage) { + if (xmppAxolotlMessage != null) { + init(message, xmppAxolotlMessage); + } else { + fail(); + } + } + }); + } else { + init(message, null); + } + } + + private void init(Message message, XmppAxolotlMessage xmppAxolotlMessage) { + this.mXmppAxolotlMessage = xmppAxolotlMessage; this.contentCreator = "initiator"; this.contentName = this.mJingleConnectionManager.nextRandomId(); this.message = message; @@ -271,13 +310,16 @@ public class JingleConnection implements Transferable { this.contentCreator = content.getAttribute("creator"); this.contentName = content.getAttribute("name"); this.transportId = content.getTransportId(); - this.mergeCandidates(JingleCandidate.parse(content.socks5transport() - .getChildren())); + this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren())); this.fileOffer = packet.getJingleContent().getFileOffer(); mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null); if (fileOffer != null) { + Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX); + if (encrypted != null) { + this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().toBareJid()); + } Element fileSize = fileOffer.findChild("size"); Element fileNameElement = fileOffer.findChild("name"); if (fileNameElement != null) { @@ -323,10 +365,9 @@ public class JingleConnection implements Transferable { message.setBody(Long.toString(size)); conversation.add(message); mXmppConnectionService.updateConversationUi(); - if (size <= ConversationsPlusPreferences.autoAcceptFileSize() - && mXmppConnectionService.isDownloadAllowedInConnection()) { - Logging.d(Config.LOGTAG, "auto accepting file from " - + packet.getFrom()); + if (mJingleConnectionManager.hasStoragePermission() + && size < ConversationsPlusPreferences.autoAcceptFileSize()) { + Logging.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom()); this.acceptedAutomatically = true; this.sendAccept(); } else { @@ -336,21 +377,36 @@ public class JingleConnection implements Transferable { + size + " allowed size:" + ConversationsPlusPreferences.autoAcceptFileSize()); - this.mXmppConnectionService.getNotificationService() - .push(message); + this.mXmppConnectionService.getNotificationService().push(message); } this.file = FileBackend.getFile(message, false); - if (message.getEncryption() == Message.ENCRYPTION_OTR) { + if (mXmppAxolotlMessage != null) { + XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage); + if (transportMessage != null) { + message.setEncryption(Message.ENCRYPTION_AXOLOTL); + this.file.setKey(transportMessage.getKey()); + this.file.setIv(transportMessage.getIv()); + message.setAxolotlFingerprint(transportMessage.getFingerprint()); + } else { + Logging.d(Config.LOGTAG,"could not process KeyTransportMessage"); + } + } else if (message.getEncryption() == Message.ENCRYPTION_OTR) { byte[] key = conversation.getSymmetricKey(); if (key == null) { this.sendCancel(); this.fail(); return; } else { - this.file.setKey(key); + this.file.setKeyAndIv(key); } } - this.file.setExpectedSize(size); + this.mFileOutputStream = AbstractConnectionManager.createOutputStream(this.file,message.getEncryption() == Message.ENCRYPTION_AXOLOTL); + if (message.getEncryption() == Message.ENCRYPTION_OTR && Config.REPORT_WRONG_FILESIZE_IN_OTR_JINGLE) { + this.file.setExpectedSize((size / 16 + 1) * 16); + } else { + this.file.setExpectedSize(size); + } + Logging.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize()); } else { this.sendCancel(); this.fail(); @@ -367,17 +423,34 @@ public class JingleConnection implements Transferable { if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { content.setTransportId(this.transportId); this.file = FileBackend.getFile(message, false); - if (message.getEncryption() == Message.ENCRYPTION_OTR) { - Conversation conversation = this.message.getConversation(); - if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key"); - cancel(); + Pair<InputStream,Integer> pair; + try { + if (message.getEncryption() == Message.ENCRYPTION_OTR) { + Conversation conversation = this.message.getConversation(); + if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not set symmetric key"); + cancel(); + } + this.file.setKeyAndIv(conversation.getSymmetricKey()); + pair = AbstractConnectionManager.createInputStream(this.file, false); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, true); + } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + this.file.setKey(mXmppAxolotlMessage.getInnerKey()); + this.file.setIv(mXmppAxolotlMessage.getIV()); + pair = AbstractConnectionManager.createInputStream(this.file, true); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, false).addChild(mXmppAxolotlMessage.toElement()); + } else { + pair = AbstractConnectionManager.createInputStream(this.file, false); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, false); } - content.setFileOffer(this.file, true); - this.file.setKey(conversation.getSymmetricKey()); - } else { - content.setFileOffer(this.file, false); + } catch (FileNotFoundException e) { + cancel(); + return; } + this.mFileInputStream = pair.first; this.transportId = this.mJingleConnectionManager.nextRandomId(); content.setTransportId(this.transportId); content.socks5transport().setChildren(getCandidatesAsElements()); @@ -386,7 +459,7 @@ public class JingleConnection implements Transferable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() != IqPacket.TYPE.ERROR) { + if (packet.getType() == IqPacket.TYPE.RESULT) { Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": other party received offer"); mJingleStatus = JINGLE_STATUS_INITIATED; mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED); @@ -571,12 +644,11 @@ public class JingleConnection implements Transferable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.ERROR) { + if (packet.getType() != IqPacket.TYPE.RESULT) { onProxyActivated.failed(); } else { onProxyActivated.success(); - sendProxyActivated(connection - .getCandidate().getCid()); + sendProxyActivated(connection.getCandidate().getCid()); } } }); @@ -675,8 +747,7 @@ public class JingleConnection implements Transferable { 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)); + content.ibbTransport().setAttribute("block-size",this.ibbBlockSize); answer.setContent(content); this.sendJinglePacket(answer); return true; @@ -749,6 +820,8 @@ public class JingleConnection implements Transferable { if (this.transport != null && this.transport instanceof JingleInbandTransport) { this.transport.disconnect(); } + StreamUtil.close(mFileInputStream); + StreamUtil.close(mFileOutputStream); if (this.message != null) { if (this.responder.equals(account.getJid())) { this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED)); @@ -902,10 +975,7 @@ public class JingleConnection implements Transferable { public void updateProgress(int i) { this.mProgress = i; - if (SystemClock.elapsedRealtime() - this.mLastGuiRefresh > Config.PROGRESS_UI_UPDATE_INTERVAL) { - this.mLastGuiRefresh = SystemClock.elapsedRealtime(); - mXmppConnectionService.updateConversationUi(); - } + mXmppConnectionService.updateConversationUi(); } interface OnProxyActivated { @@ -957,4 +1027,8 @@ public class JingleConnection implements Transferable { public int getProgress() { return this.mProgress; } + + public AbstractConnectionManager getConnectionManager() { + return this.mJingleConnectionManager; + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java index 861082d8..3284a936 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java @@ -1,23 +1,23 @@ package de.thedevstack.conversationsplus.xmpp.jingle; +import android.annotation.SuppressLint; + 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 de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.entities.Account; -import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.services.AbstractConnectionManager; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.utils.Xmlns; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; -import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.xmpp.jingle.stanzas.JinglePacket; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; @@ -83,7 +83,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { public void getPrimaryCandidate(Account account, final OnPrimaryCandidateFound listener) { - if (Config.NO_PROXY_LOOKUP) { + if (Config.DISABLE_PROXY_LOOKUP) { listener.onPrimaryCandidateFound(false, null); return; } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleInbandTransport.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleInbandTransport.java index 84d40b3e..7c830d0b 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleInbandTransport.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleInbandTransport.java @@ -1,5 +1,7 @@ package de.thedevstack.conversationsplus.xmpp.jingle; +import android.util.Base64; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -7,15 +9,12 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; -import android.util.Base64; - import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.utils.StreamUtil; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.DownloadableFile; -import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.utils.CryptoHelper; -import de.thedevstack.conversationsplus.utils.StreamUtil; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; import de.thedevstack.conversationsplus.xmpp.jid.Jid; @@ -75,7 +74,7 @@ public class JingleInbandTransport extends JingleTransport { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.ERROR) { + if (packet.getType() != IqPacket.TYPE.RESULT) { callback.failed(); } else { callback.established(); @@ -94,7 +93,7 @@ public class JingleInbandTransport extends JingleTransport { digest.reset(); file.getParentFile().mkdirs(); file.createNewFile(); - this.fileOutputStream = file.createOutputStream(); + this.fileOutputStream = connection.getFileOutputStream(); if (this.fileOutputStream == null) { Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream"); callback.onFileTransferAborted(); @@ -113,15 +112,11 @@ public class JingleInbandTransport extends JingleTransport { this.onFileTransmissionStatusChanged = callback; this.file = file; try { - if (this.file.getKey() != null) { - this.remainingSize = (this.file.getSize() / 16 + 1) * 16; - } else { - this.remainingSize = this.file.getSize(); - } + this.remainingSize = this.file.getExpectedSize(); this.fileSize = this.remainingSize; this.digest = MessageDigest.getInstance("SHA-1"); this.digest.reset(); - fileInputStream = this.file.createInputStream(); + fileInputStream = connection.getFileInputStream(); if (fileInputStream == null) { Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream"); callback.onFileTransferAborted(); @@ -181,6 +176,7 @@ public class JingleInbandTransport extends JingleTransport { data.setAttribute("sid", this.sessionId); data.setContent(base64); this.account.getXmppConnection().sendIqPacket(iq, this.onAckReceived); + this.account.getXmppConnection().r(); //don't fill up stanza queue too much this.seq++; if (this.remainingSize > 0) { connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100)); diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleSocks5Transport.java index 999180c5..9ea268d5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleSocks5Transport.java @@ -1,5 +1,7 @@ package de.thedevstack.conversationsplus.xmpp.jingle; +import android.os.PowerManager; + import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -10,14 +12,13 @@ import java.net.SocketAddress; import java.net.UnknownHostException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.utils.StreamUtil; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.entities.DownloadableFile; -import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.utils.CryptoHelper; -import de.thedevstack.conversationsplus.utils.StreamUtil; +import de.thedevstack.conversationsplus.utils.SocksSocketFactory; public class JingleSocks5Transport extends JingleTransport { private JingleCandidate candidate; @@ -61,31 +62,12 @@ public class JingleSocks5Transport extends JingleTransport { socket = new Socket(); SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort()); socket.connect(address,Config.SOCKET_TIMEOUT * 1000); + 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(); - } + SocksSocketFactory.createSocksConnection(socket,destination,0); + isEstablished = true; + callback.established(); } catch (UnknownHostException e) { callback.failed(); } catch (IOException e) { @@ -96,23 +78,24 @@ public class JingleSocks5Transport extends JingleTransport { } - public void send(final DownloadableFile file, - final OnFileTransmissionStatusChanged callback) { + public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) { new Thread(new Runnable() { @Override public void run() { InputStream fileInputStream = null; + final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_"+connection.getSessionId()); try { + wakeLock.acquire(); MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.reset(); - fileInputStream = file.createInputStream(); + fileInputStream = connection.getFileInputStream(); if (fileInputStream == null) { Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream"); callback.onFileTransferAborted(); return; } - long size = file.getSize(); + long size = file.getExpectedSize(); long transmitted = 0; int count; byte[] buffer = new byte[8192]; @@ -138,6 +121,7 @@ public class JingleSocks5Transport extends JingleTransport { callback.onFileTransferAborted(); } finally { StreamUtil.close(fileInputStream); + wakeLock.release(); } } }).start(); @@ -150,14 +134,16 @@ public class JingleSocks5Transport extends JingleTransport { @Override public void run() { OutputStream fileOutputStream = null; + final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_"+connection.getSessionId()); try { + wakeLock.acquire(); MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.reset(); - inputStream.skip(45); + //inputStream.skip(45); socket.setSoTimeout(30000); file.getParentFile().mkdirs(); file.createNewFile(); - fileOutputStream = file.createOutputStream(); + fileOutputStream = connection.getFileOutputStream(); if (fileOutputStream == null) { callback.onFileTransferAborted(); Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream"); @@ -166,7 +152,7 @@ public class JingleSocks5Transport extends JingleTransport { double size = file.getExpectedSize(); long remainingSize = file.getExpectedSize(); byte[] buffer = new byte[8192]; - int count = buffer.length; + int count; while (remainingSize > 0) { count = inputStream.read(buffer); if (count == -1) { @@ -194,7 +180,9 @@ public class JingleSocks5Transport extends JingleTransport { Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage()); callback.onFileTransferAborted(); } finally { + wakeLock.release(); StreamUtil.close(fileOutputStream); + StreamUtil.close(inputStream); } } }).start(); @@ -209,27 +197,9 @@ public class JingleSocks5Transport extends JingleTransport { } public void disconnect() { - if (this.outputStream != null) { - try { - this.outputStream.close(); - } catch (IOException e) { - - } - } - if (this.inputStream != null) { - try { - this.inputStream.close(); - } catch (IOException e) { - - } - } - if (this.socket != null) { - try { - this.socket.close(); - } catch (IOException e) { - - } - } + StreamUtil.close(inputStream); + StreamUtil.close(outputStream); + StreamUtil.close(socket); } public boolean isEstablished() { diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnFileTransmissionStatusChanged.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnFileTransmissionStatusChanged.java index 86daae81..762b4551 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnFileTransmissionStatusChanged.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnFileTransmissionStatusChanged.java @@ -3,7 +3,7 @@ package de.thedevstack.conversationsplus.xmpp.jingle; import de.thedevstack.conversationsplus.entities.DownloadableFile; public interface OnFileTransmissionStatusChanged { - public void onFileTransmitted(DownloadableFile file); + void onFileTransmitted(DownloadableFile file); - public void onFileTransferAborted(); + void onFileTransferAborted(); } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnJinglePacketReceived.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnJinglePacketReceived.java index d6dda138..c87fd400 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnJinglePacketReceived.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnJinglePacketReceived.java @@ -5,5 +5,5 @@ import de.thedevstack.conversationsplus.xmpp.PacketReceived; import de.thedevstack.conversationsplus.xmpp.jingle.stanzas.JinglePacket; public interface OnJinglePacketReceived extends PacketReceived { - public void onJinglePacketReceived(Account account, JinglePacket packet); + void onJinglePacketReceived(Account account, JinglePacket packet); } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnPrimaryCandidateFound.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnPrimaryCandidateFound.java index be1f1d02..4fe31d3b 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnPrimaryCandidateFound.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/OnPrimaryCandidateFound.java @@ -1,6 +1,5 @@ package de.thedevstack.conversationsplus.xmpp.jingle; public interface OnPrimaryCandidateFound { - public void onPrimaryCandidateFound(boolean success, - JingleCandidate canditate); + void onPrimaryCandidateFound(boolean success, JingleCandidate canditate); } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/stanzas/Content.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/stanzas/Content.java index 40eec6f1..336a8c14 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/stanzas/Content.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/stanzas/Content.java @@ -25,17 +25,18 @@ public class Content extends Element { this.transportId = sid; } - public void setFileOffer(DownloadableFile actualFile, boolean otr) { + public Element 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())); + file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize())); if (otr) { file.addChild("name").setContent(actualFile.getName() + ".otr"); } else { file.addChild("name").setContent(actualFile.getName()); } + return file; } public Element getFileOffer() { diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/pep/Avatar.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/pep/Avatar.java index 31fda8fd..9c1c8b9c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/pep/Avatar.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/pep/Avatar.java @@ -1,10 +1,10 @@ package de.thedevstack.conversationsplus.xmpp.pep; +import android.util.Base64; + import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.jid.Jid; -import android.util.Base64; - public class Avatar { public enum Origin { PEP, VCARD }; diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractAcknowledgeableStanza.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractAcknowledgeableStanza.java new file mode 100644 index 00000000..49d9f799 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractAcknowledgeableStanza.java @@ -0,0 +1,31 @@ +package de.thedevstack.conversationsplus.xmpp.stanzas; + +import de.thedevstack.conversationsplus.xml.Element; + +abstract public class AbstractAcknowledgeableStanza extends AbstractStanza { + + protected AbstractAcknowledgeableStanza(String name) { + super(name); + } + + + public String getId() { + return this.getAttribute("id"); + } + + public void setId(final String id) { + setAttribute("id", id); + } + + public Element getError() { + Element error = findChild("error"); + if (error != null) { + for(Element element : error.getChildren()) { + if (!element.getName().equals("text")) { + return element; + } + } + } + return null; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractStanza.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractStanza.java index dff5b58d..78c71541 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractStanza.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/AbstractStanza.java @@ -18,10 +18,6 @@ public class AbstractStanza extends Element { return getAttributeAsJid("from"); } - public String getId() { - return this.getAttribute("id"); - } - public void setTo(final Jid to) { if (to != null) { setAttribute("to", to.toString()); @@ -34,10 +30,6 @@ public class AbstractStanza extends Element { } } - public void setId(final String id) { - setAttribute("id", id); - } - public boolean fromServer(final Account account) { return getFrom() == null || getFrom().equals(account.getServer()) diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacket.java index fb08556b..d86831e0 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacket.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacket.java @@ -2,14 +2,15 @@ package de.thedevstack.conversationsplus.xmpp.stanzas; import de.thedevstack.conversationsplus.xml.Element; -public class IqPacket extends AbstractStanza { +public class IqPacket extends AbstractAcknowledgeableStanza { - public static enum TYPE { + public enum TYPE { ERROR, SET, RESULT, GET, - INVALID + INVALID, + TIMEOUT } public IqPacket(final TYPE type) { @@ -39,6 +40,9 @@ public class IqPacket extends AbstractStanza { public TYPE getType() { final String type = getAttribute("type"); + if (type == null) { + return TYPE.INVALID; + } switch (type) { case "error": return TYPE.ERROR; @@ -48,6 +52,8 @@ public class IqPacket extends AbstractStanza { return TYPE.SET; case "get": return TYPE.GET; + case "timeout": + return TYPE.TIMEOUT; default: return TYPE.INVALID; } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java index 638ba5f4..e14cdacd 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java @@ -2,12 +2,10 @@ package de.thedevstack.conversationsplus.xmpp.stanzas; import android.util.Pair; -import java.text.ParseException; - import de.thedevstack.conversationsplus.parser.AbstractParser; import de.thedevstack.conversationsplus.xml.Element; -public class MessagePacket extends AbstractStanza { +public class MessagePacket extends AbstractAcknowledgeableStanza { public static final int TYPE_CHAT = 0; public static final int TYPE_NORMAL = 2; public static final int TYPE_GROUPCHAT = 3; @@ -29,6 +27,11 @@ public class MessagePacket extends AbstractStanza { this.children.add(0, body); } + public void setAxolotlMessage(Element axolotlMessage) { + this.children.remove(findChild("body")); + this.children.add(0, axolotlMessage); + } + public void setType(int type) { switch (type) { case TYPE_CHAT: diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/PresencePacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/PresencePacket.java index dfe40f87..5fd21b3b 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/PresencePacket.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/PresencePacket.java @@ -1,6 +1,6 @@ package de.thedevstack.conversationsplus.xmpp.stanzas; -public class PresencePacket extends AbstractStanza { +public class PresencePacket extends AbstractAcknowledgeableStanza { public PresencePacket() { super("presence"); diff --git a/src/main/java/de/tzur/conversations/Settings.java b/src/main/java/de/tzur/conversations/Settings.java index 2484a88f..5d64f084 100644 --- a/src/main/java/de/tzur/conversations/Settings.java +++ b/src/main/java/de/tzur/conversations/Settings.java @@ -3,7 +3,6 @@ package de.tzur.conversations; import android.content.SharedPreferences; import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.ConversationsPlusApplication; /** * This class is used to provide access to settings which have to be accessed frequently. diff --git a/src/main/res/drawable-hdpi/ic_action_done.png b/src/main/res/drawable-hdpi/ic_action_done.png Binary files differnew file mode 100644 index 00000000..58bf9721 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_action_done.png diff --git a/src/main/res/drawable-hdpi/ic_done_black_24dp.png b/src/main/res/drawable-hdpi/ic_done_black_24dp.png Binary files differnew file mode 100644 index 00000000..d4c06072 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_done_black_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_link_white_24dp.png b/src/main/res/drawable-hdpi/ic_link_white_24dp.png Binary files differnew file mode 100644 index 00000000..4186e00c --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_link_white_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_lock_black_18dp.png b/src/main/res/drawable-hdpi/ic_lock_black_18dp.png Binary files differnew file mode 100644 index 00000000..4c7a7c59 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_lock_black_18dp.png diff --git a/src/main/res/drawable-hdpi/ic_lock_white_18dp.png b/src/main/res/drawable-hdpi/ic_lock_white_18dp.png Binary files differnew file mode 100644 index 00000000..29e8bfd3 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_lock_white_18dp.png diff --git a/src/main/res/drawable-hdpi/ic_notifications_grey600_24dp.png b/src/main/res/drawable-hdpi/ic_notifications_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..96b329c4 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_notifications_grey600_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_notifications_none_grey600_24dp.png b/src/main/res/drawable-hdpi/ic_notifications_none_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..6cd4dfc9 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_notifications_none_grey600_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_notifications_off_grey600_24dp.png b/src/main/res/drawable-hdpi/ic_notifications_off_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..09ebc5d2 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_notifications_off_grey600_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_notifications_paused_grey600_24dp.png b/src/main/res/drawable-hdpi/ic_notifications_paused_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..e92d43ac --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_notifications_paused_grey600_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png b/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..51cc4dbd --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_refresh_grey600_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_secure_indicator.png b/src/main/res/drawable-hdpi/ic_secure_indicator.png Binary files differdeleted file mode 100644 index 2a2934fb..00000000 --- a/src/main/res/drawable-hdpi/ic_secure_indicator.png +++ /dev/null diff --git a/src/main/res/drawable-hdpi/message_bubble_received.9.png b/src/main/res/drawable-hdpi/message_bubble_received.9.png Binary files differnew file mode 100644 index 00000000..dfd857cb --- /dev/null +++ b/src/main/res/drawable-hdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-hdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-hdpi/message_bubble_received_warning.9.png Binary files differnew file mode 100644 index 00000000..fd07bc20 --- /dev/null +++ b/src/main/res/drawable-hdpi/message_bubble_received_warning.9.png diff --git a/src/main/res/drawable-hdpi/message_bubble_received_white.9.png b/src/main/res/drawable-hdpi/message_bubble_received_white.9.png Binary files differnew file mode 100644 index 00000000..bec20798 --- /dev/null +++ b/src/main/res/drawable-hdpi/message_bubble_received_white.9.png diff --git a/src/main/res/drawable-hdpi/message_bubble_sent.9.png b/src/main/res/drawable-hdpi/message_bubble_sent.9.png Binary files differnew file mode 100644 index 00000000..10dc2e29 --- /dev/null +++ b/src/main/res/drawable-hdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-hdpi/switch_thumb_disable.png b/src/main/res/drawable-hdpi/switch_thumb_disable.png Binary files differnew file mode 100644 index 00000000..1e9b151b --- /dev/null +++ b/src/main/res/drawable-hdpi/switch_thumb_disable.png diff --git a/src/main/res/drawable-hdpi/switch_thumb_off_normal.png b/src/main/res/drawable-hdpi/switch_thumb_off_normal.png Binary files differnew file mode 100644 index 00000000..b7c1fc11 --- /dev/null +++ b/src/main/res/drawable-hdpi/switch_thumb_off_normal.png diff --git a/src/main/res/drawable-hdpi/switch_thumb_off_pressed.png b/src/main/res/drawable-hdpi/switch_thumb_off_pressed.png Binary files differnew file mode 100644 index 00000000..ca6e4909 --- /dev/null +++ b/src/main/res/drawable-hdpi/switch_thumb_off_pressed.png diff --git a/src/main/res/drawable-hdpi/switch_thumb_on_normal.png b/src/main/res/drawable-hdpi/switch_thumb_on_normal.png Binary files differnew file mode 100644 index 00000000..cbcda5d4 --- /dev/null +++ b/src/main/res/drawable-hdpi/switch_thumb_on_normal.png diff --git a/src/main/res/drawable-hdpi/switch_thumb_on_pressed.png b/src/main/res/drawable-hdpi/switch_thumb_on_pressed.png Binary files differnew file mode 100644 index 00000000..234b12dc --- /dev/null +++ b/src/main/res/drawable-hdpi/switch_thumb_on_pressed.png diff --git a/src/main/res/drawable-mdpi/ic_action_done.png b/src/main/res/drawable-mdpi/ic_action_done.png Binary files differnew file mode 100644 index 00000000..cf5fab3a --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_action_done.png diff --git a/src/main/res/drawable-mdpi/ic_done_black_24dp.png b/src/main/res/drawable-mdpi/ic_done_black_24dp.png Binary files differnew file mode 100644 index 00000000..5e5e7cf2 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_done_black_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_link_white_24dp.png b/src/main/res/drawable-mdpi/ic_link_white_24dp.png Binary files differnew file mode 100644 index 00000000..6960502e --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_link_white_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_lock_black_18dp.png b/src/main/res/drawable-mdpi/ic_lock_black_18dp.png Binary files differnew file mode 100644 index 00000000..c8b6fe71 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_lock_black_18dp.png diff --git a/src/main/res/drawable-mdpi/ic_lock_white_18dp.png b/src/main/res/drawable-mdpi/ic_lock_white_18dp.png Binary files differnew file mode 100644 index 00000000..1265e98e --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_lock_white_18dp.png diff --git a/src/main/res/drawable-mdpi/ic_notifications_grey600_24dp.png b/src/main/res/drawable-mdpi/ic_notifications_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..d6c20c20 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_notifications_grey600_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_notifications_none_grey600_24dp.png b/src/main/res/drawable-mdpi/ic_notifications_none_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..3e8b0805 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_notifications_none_grey600_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_notifications_off_grey600_24dp.png b/src/main/res/drawable-mdpi/ic_notifications_off_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..af3b6321 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_notifications_off_grey600_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_notifications_paused_grey600_24dp.png b/src/main/res/drawable-mdpi/ic_notifications_paused_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..9d6308d2 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_notifications_paused_grey600_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png b/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..c136c59f --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_refresh_grey600_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_secure_indicator.png b/src/main/res/drawable-mdpi/ic_secure_indicator.png Binary files differdeleted file mode 100644 index 5a73aef4..00000000 --- a/src/main/res/drawable-mdpi/ic_secure_indicator.png +++ /dev/null diff --git a/src/main/res/drawable-mdpi/message_bubble_received.9.png b/src/main/res/drawable-mdpi/message_bubble_received.9.png Binary files differnew file mode 100644 index 00000000..9835a736 --- /dev/null +++ b/src/main/res/drawable-mdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-mdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-mdpi/message_bubble_received_warning.9.png Binary files differnew file mode 100644 index 00000000..ff8f80b6 --- /dev/null +++ b/src/main/res/drawable-mdpi/message_bubble_received_warning.9.png diff --git a/src/main/res/drawable-mdpi/message_bubble_received_white.9.png b/src/main/res/drawable-mdpi/message_bubble_received_white.9.png Binary files differnew file mode 100644 index 00000000..d7a3bb5d --- /dev/null +++ b/src/main/res/drawable-mdpi/message_bubble_received_white.9.png diff --git a/src/main/res/drawable-mdpi/message_bubble_sent.9.png b/src/main/res/drawable-mdpi/message_bubble_sent.9.png Binary files differnew file mode 100644 index 00000000..596699bb --- /dev/null +++ b/src/main/res/drawable-mdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-mdpi/switch_thumb_disable.png b/src/main/res/drawable-mdpi/switch_thumb_disable.png Binary files differnew file mode 100644 index 00000000..968de345 --- /dev/null +++ b/src/main/res/drawable-mdpi/switch_thumb_disable.png diff --git a/src/main/res/drawable-mdpi/switch_thumb_off_normal.png b/src/main/res/drawable-mdpi/switch_thumb_off_normal.png Binary files differnew file mode 100644 index 00000000..51fb4d7a --- /dev/null +++ b/src/main/res/drawable-mdpi/switch_thumb_off_normal.png diff --git a/src/main/res/drawable-mdpi/switch_thumb_off_pressed.png b/src/main/res/drawable-mdpi/switch_thumb_off_pressed.png Binary files differnew file mode 100644 index 00000000..ca788445 --- /dev/null +++ b/src/main/res/drawable-mdpi/switch_thumb_off_pressed.png diff --git a/src/main/res/drawable-mdpi/switch_thumb_on_normal.png b/src/main/res/drawable-mdpi/switch_thumb_on_normal.png Binary files differnew file mode 100644 index 00000000..6a93d5f7 --- /dev/null +++ b/src/main/res/drawable-mdpi/switch_thumb_on_normal.png diff --git a/src/main/res/drawable-mdpi/switch_thumb_on_pressed.png b/src/main/res/drawable-mdpi/switch_thumb_on_pressed.png Binary files differnew file mode 100644 index 00000000..e8d7bd0f --- /dev/null +++ b/src/main/res/drawable-mdpi/switch_thumb_on_pressed.png diff --git a/src/main/res/drawable-xhdpi/ic_action_done.png b/src/main/res/drawable-xhdpi/ic_action_done.png Binary files differnew file mode 100644 index 00000000..b8915716 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_action_done.png diff --git a/src/main/res/drawable-xhdpi/ic_done_black_24dp.png b/src/main/res/drawable-xhdpi/ic_done_black_24dp.png Binary files differnew file mode 100644 index 00000000..64a4944f --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_done_black_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_link_white_24dp.png b/src/main/res/drawable-xhdpi/ic_link_white_24dp.png Binary files differnew file mode 100644 index 00000000..984b572a --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_link_white_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_lock_black_18dp.png b/src/main/res/drawable-xhdpi/ic_lock_black_18dp.png Binary files differnew file mode 100644 index 00000000..0888c617 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_lock_black_18dp.png diff --git a/src/main/res/drawable-xhdpi/ic_lock_white_18dp.png b/src/main/res/drawable-xhdpi/ic_lock_white_18dp.png Binary files differnew file mode 100644 index 00000000..b94735ec --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_lock_white_18dp.png diff --git a/src/main/res/drawable-xhdpi/ic_notifications_grey600_24dp.png b/src/main/res/drawable-xhdpi/ic_notifications_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..d441dc7c --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_notifications_grey600_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_notifications_none_grey600_24dp.png b/src/main/res/drawable-xhdpi/ic_notifications_none_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..1123cea4 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_notifications_none_grey600_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_notifications_off_grey600_24dp.png b/src/main/res/drawable-xhdpi/ic_notifications_off_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..aef303a0 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_notifications_off_grey600_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_notifications_paused_grey600_24dp.png b/src/main/res/drawable-xhdpi/ic_notifications_paused_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..c68bedd3 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_notifications_paused_grey600_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png b/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..7891efff --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_refresh_grey600_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_secure_indicator.png b/src/main/res/drawable-xhdpi/ic_secure_indicator.png Binary files differdeleted file mode 100644 index 1f4c9a32..00000000 --- a/src/main/res/drawable-xhdpi/ic_secure_indicator.png +++ /dev/null diff --git a/src/main/res/drawable-xhdpi/message_bubble_received.9.png b/src/main/res/drawable-xhdpi/message_bubble_received.9.png Binary files differnew file mode 100644 index 00000000..c0eb47eb --- /dev/null +++ b/src/main/res/drawable-xhdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-xhdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-xhdpi/message_bubble_received_warning.9.png Binary files differnew file mode 100644 index 00000000..fe0324ce --- /dev/null +++ b/src/main/res/drawable-xhdpi/message_bubble_received_warning.9.png diff --git a/src/main/res/drawable-xhdpi/message_bubble_received_white.9.png b/src/main/res/drawable-xhdpi/message_bubble_received_white.9.png Binary files differnew file mode 100644 index 00000000..fdb6be0d --- /dev/null +++ b/src/main/res/drawable-xhdpi/message_bubble_received_white.9.png diff --git a/src/main/res/drawable-xhdpi/message_bubble_sent.9.png b/src/main/res/drawable-xhdpi/message_bubble_sent.9.png Binary files differnew file mode 100644 index 00000000..cb5654b7 --- /dev/null +++ b/src/main/res/drawable-xhdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-xhdpi/switch_thumb_disable.png b/src/main/res/drawable-xhdpi/switch_thumb_disable.png Binary files differnew file mode 100644 index 00000000..7f677324 --- /dev/null +++ b/src/main/res/drawable-xhdpi/switch_thumb_disable.png diff --git a/src/main/res/drawable-xhdpi/switch_thumb_off_normal.png b/src/main/res/drawable-xhdpi/switch_thumb_off_normal.png Binary files differnew file mode 100644 index 00000000..4199d322 --- /dev/null +++ b/src/main/res/drawable-xhdpi/switch_thumb_off_normal.png diff --git a/src/main/res/drawable-xhdpi/switch_thumb_off_pressed.png b/src/main/res/drawable-xhdpi/switch_thumb_off_pressed.png Binary files differnew file mode 100644 index 00000000..2b86888f --- /dev/null +++ b/src/main/res/drawable-xhdpi/switch_thumb_off_pressed.png diff --git a/src/main/res/drawable-xhdpi/switch_thumb_on_normal.png b/src/main/res/drawable-xhdpi/switch_thumb_on_normal.png Binary files differnew file mode 100644 index 00000000..daa30990 --- /dev/null +++ b/src/main/res/drawable-xhdpi/switch_thumb_on_normal.png diff --git a/src/main/res/drawable-xhdpi/switch_thumb_on_pressed.png b/src/main/res/drawable-xhdpi/switch_thumb_on_pressed.png Binary files differnew file mode 100644 index 00000000..6aab47c9 --- /dev/null +++ b/src/main/res/drawable-xhdpi/switch_thumb_on_pressed.png diff --git a/src/main/res/drawable-xxhdpi/ic_done_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_done_black_24dp.png Binary files differnew file mode 100644 index 00000000..c9c01741 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_done_black_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_link_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_link_white_24dp.png Binary files differnew file mode 100644 index 00000000..8e96c356 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_link_white_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png b/src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png Binary files differnew file mode 100644 index 00000000..dbcf3f33 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_lock_black_18dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_lock_white_18dp.png b/src/main/res/drawable-xxhdpi/ic_lock_white_18dp.png Binary files differnew file mode 100644 index 00000000..895aabbf --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_lock_white_18dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_notifications_grey600_24dp.png b/src/main/res/drawable-xxhdpi/ic_notifications_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..7d58d25d --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_notifications_grey600_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_notifications_none_grey600_24dp.png b/src/main/res/drawable-xxhdpi/ic_notifications_none_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..b8772d37 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_notifications_none_grey600_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_notifications_off_grey600_24dp.png b/src/main/res/drawable-xxhdpi/ic_notifications_off_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..e627b30a --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_notifications_off_grey600_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_notifications_paused_grey600_24dp.png b/src/main/res/drawable-xxhdpi/ic_notifications_paused_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..e38f5217 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_notifications_paused_grey600_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png b/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..9c1e27d7 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_refresh_grey600_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_secure_indicator.png b/src/main/res/drawable-xxhdpi/ic_secure_indicator.png Binary files differdeleted file mode 100644 index 1ee9b67d..00000000 --- a/src/main/res/drawable-xxhdpi/ic_secure_indicator.png +++ /dev/null diff --git a/src/main/res/drawable-xxhdpi/message_bubble_received.9.png b/src/main/res/drawable-xxhdpi/message_bubble_received.9.png Binary files differnew file mode 100644 index 00000000..10e78408 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-xxhdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-xxhdpi/message_bubble_received_warning.9.png Binary files differnew file mode 100644 index 00000000..53ecbecf --- /dev/null +++ b/src/main/res/drawable-xxhdpi/message_bubble_received_warning.9.png diff --git a/src/main/res/drawable-xxhdpi/message_bubble_received_white.9.png b/src/main/res/drawable-xxhdpi/message_bubble_received_white.9.png Binary files differnew file mode 100644 index 00000000..436a1bd3 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/message_bubble_received_white.9.png diff --git a/src/main/res/drawable-xxhdpi/message_bubble_sent.9.png b/src/main/res/drawable-xxhdpi/message_bubble_sent.9.png Binary files differnew file mode 100644 index 00000000..f78425d2 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-xxhdpi/switch_thumb_disable.png b/src/main/res/drawable-xxhdpi/switch_thumb_disable.png Binary files differnew file mode 100644 index 00000000..db7c1df4 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/switch_thumb_disable.png diff --git a/src/main/res/drawable-xxhdpi/switch_thumb_off_normal.png b/src/main/res/drawable-xxhdpi/switch_thumb_off_normal.png Binary files differnew file mode 100644 index 00000000..f747b559 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/switch_thumb_off_normal.png diff --git a/src/main/res/drawable-xxhdpi/switch_thumb_off_pressed.png b/src/main/res/drawable-xxhdpi/switch_thumb_off_pressed.png Binary files differnew file mode 100644 index 00000000..b9fe6d46 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/switch_thumb_off_pressed.png diff --git a/src/main/res/drawable-xxhdpi/switch_thumb_on_normal.png b/src/main/res/drawable-xxhdpi/switch_thumb_on_normal.png Binary files differnew file mode 100644 index 00000000..88199024 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/switch_thumb_on_normal.png diff --git a/src/main/res/drawable-xxhdpi/switch_thumb_on_pressed.png b/src/main/res/drawable-xxhdpi/switch_thumb_on_pressed.png Binary files differnew file mode 100644 index 00000000..7a4fed54 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/switch_thumb_on_pressed.png diff --git a/src/main/res/drawable-xxxhdpi/ic_done_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_done_black_24dp.png Binary files differnew file mode 100644 index 00000000..2f6d6386 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_done_black_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_link_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_link_white_24dp.png Binary files differnew file mode 100644 index 00000000..df2faf36 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_link_white_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_lock_black_18dp.png b/src/main/res/drawable-xxxhdpi/ic_lock_black_18dp.png Binary files differnew file mode 100644 index 00000000..c49d420e --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_lock_black_18dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_lock_white_18dp.png b/src/main/res/drawable-xxxhdpi/ic_lock_white_18dp.png Binary files differnew file mode 100644 index 00000000..0dcada81 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_lock_white_18dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_notifications_grey600_24dp.png b/src/main/res/drawable-xxxhdpi/ic_notifications_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..98dff2f3 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_notifications_grey600_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_notifications_none_grey600_24dp.png b/src/main/res/drawable-xxxhdpi/ic_notifications_none_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..ffdb15a8 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_notifications_none_grey600_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_notifications_off_grey600_24dp.png b/src/main/res/drawable-xxxhdpi/ic_notifications_off_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..ab65f4b2 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_notifications_off_grey600_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_notifications_paused_grey600_24dp.png b/src/main/res/drawable-xxxhdpi/ic_notifications_paused_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..53162e56 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_notifications_paused_grey600_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_refresh_grey600_24dp.png b/src/main/res/drawable-xxxhdpi/ic_refresh_grey600_24dp.png Binary files differnew file mode 100644 index 00000000..e44a6d28 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_refresh_grey600_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_received.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_received.9.png Binary files differnew file mode 100644 index 00000000..c474359e --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_received_warning.9.png Binary files differnew file mode 100644 index 00000000..1421768c --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/message_bubble_received_warning.9.png diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_received_white.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_received_white.9.png Binary files differnew file mode 100644 index 00000000..ee89b670 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/message_bubble_received_white.9.png diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_sent.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_sent.9.png Binary files differnew file mode 100644 index 00000000..d34038d0 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-xxxhdpi/switch_thumb_disable.png b/src/main/res/drawable-xxxhdpi/switch_thumb_disable.png Binary files differnew file mode 100644 index 00000000..3970168c --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/switch_thumb_disable.png diff --git a/src/main/res/drawable-xxxhdpi/switch_thumb_off_normal.png b/src/main/res/drawable-xxxhdpi/switch_thumb_off_normal.png Binary files differnew file mode 100644 index 00000000..ea8d4f89 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/switch_thumb_off_normal.png diff --git a/src/main/res/drawable-xxxhdpi/switch_thumb_off_pressed.png b/src/main/res/drawable-xxxhdpi/switch_thumb_off_pressed.png Binary files differnew file mode 100644 index 00000000..84d667b1 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/switch_thumb_off_pressed.png diff --git a/src/main/res/drawable-xxxhdpi/switch_thumb_on_normal.png b/src/main/res/drawable-xxxhdpi/switch_thumb_on_normal.png Binary files differnew file mode 100644 index 00000000..06b190eb --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/switch_thumb_on_normal.png diff --git a/src/main/res/drawable-xxxhdpi/switch_thumb_on_pressed.png b/src/main/res/drawable-xxxhdpi/switch_thumb_on_pressed.png Binary files differnew file mode 100644 index 00000000..79c30d1e --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/switch_thumb_on_pressed.png diff --git a/src/main/res/drawable/message_border.xml b/src/main/res/drawable/account_image_border.xml index 990d0288..990d0288 100644 --- a/src/main/res/drawable/message_border.xml +++ b/src/main/res/drawable/account_image_border.xml diff --git a/src/main/res/drawable/switch_back_off.xml b/src/main/res/drawable/switch_back_off.xml new file mode 100644 index 00000000..20d2fb14 --- /dev/null +++ b/src/main/res/drawable/switch_back_off.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + + <item android:state_enabled="false"><shape android:shape="rectangle"> + <solid android:color="#D5D5D5" /> + + <corners android:radius="99dp" /> + </shape></item> + <item android:state_enabled="true"><shape android:shape="rectangle"> + <solid android:color="#939393" /> + + <corners android:radius="99dp" /> + </shape></item> + +</selector>
\ No newline at end of file diff --git a/src/main/res/drawable/switch_back_on.xml b/src/main/res/drawable/switch_back_on.xml new file mode 100644 index 00000000..45117a98 --- /dev/null +++ b/src/main/res/drawable/switch_back_on.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false"> + <shape android:shape="rectangle"> + <solid android:color="#D5D5D5"/> + <corners android:radius="99dp"/> + </shape> + </item> + <item android:state_enabled="true"> + <shape android:shape="rectangle"> + <!-- 30% accent on white --> + <solid android:color="#b3ddf7"/> + <corners android:radius="99dp"/> + </shape> + </item> +</selector>
\ No newline at end of file diff --git a/src/main/res/drawable/switch_thumb.xml b/src/main/res/drawable/switch_thumb.xml new file mode 100644 index 00000000..ba3d1c45 --- /dev/null +++ b/src/main/res/drawable/switch_thumb.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + + <item android:drawable="@drawable/switch_thumb_disable" android:state_enabled="false"/> + <item android:drawable="@drawable/switch_thumb_on_pressed" android:state_checked="true" android:state_pressed="true"/> + <item android:drawable="@drawable/switch_thumb_on_pressed" android:state_checked="true" android:state_focused="true"/> + <item android:drawable="@drawable/switch_thumb_on_normal" android:state_checked="true"/> + <item android:drawable="@drawable/switch_thumb_off_pressed" android:state_checked="false" android:state_pressed="true"/> + <item android:drawable="@drawable/switch_thumb_off_pressed" android:state_checked="false" android:state_focused="true"/> + <item android:drawable="@drawable/switch_thumb_off_normal" android:state_checked="false"/> + +</selector>
\ No newline at end of file diff --git a/src/main/res/layout/account_row.xml b/src/main/res/layout/account_row.xml index 77138a4a..05270e3a 100644 --- a/src/main/res/layout/account_row.xml +++ b/src/main/res/layout/account_row.xml @@ -1,20 +1,21 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?android:attr/activatedBackgroundIndicator" - android:paddingLeft="8dp" - android:paddingBottom="8dp" - android:paddingTop="8dp"> + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:attr/activatedBackgroundIndicator" + android:paddingLeft="8dp" + android:paddingBottom="8dp" + android:paddingTop="8dp"> - <ImageView + <com.makeramen.roundedimageview.RoundedImageView android:id="@+id/account_image" android:layout_width="48dp" android:layout_height="48dp" android:layout_alignParentLeft="true" android:src="@drawable/ic_profile" - android:contentDescription="@string/account_image_description"> - </ImageView> + android:contentDescription="@string/account_image_description" + app:riv_corner_radius="2dp" /> <LinearLayout android:layout_width="fill_parent" @@ -40,18 +41,19 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/account_status_unknown" - android:textColor="@color/secondaryText" + android:textColor="@color/black54" android:textSize="?attr/TextSizeBody" android:textStyle="bold" /> </LinearLayout> - <Switch + <de.thedevstack.conversationsplus.ui.widget.Switch + style="@style/MD" android:id="@+id/tgl_account_status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" - android:padding="8dp" + android:padding="16dp" android:focusable="false"/> </RelativeLayout>
\ No newline at end of file diff --git a/src/main/res/layout/activity_about.xml b/src/main/res/layout/activity_about.xml index b1b3b562..142087c0 100644 --- a/src/main/res/layout/activity_about.xml +++ b/src/main/res/layout/activity_about.xml @@ -17,5 +17,6 @@ android:paddingBottom="@dimen/activity_vertical_margin" android:textColor="@color/primaryText" android:textSize="?attr/TextSizeBody" - android:typeface="monospace"/> + android:typeface="monospace" + android:fontFamily="monospace"/> </ScrollView> diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml index 0c9809bc..58e168f3 100644 --- a/src/main/res/layout/activity_edit_account.xml +++ b/src/main/res/layout/activity_edit_account.xml @@ -1,108 +1,165 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@color/grey200" > + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/grey200"> <ScrollView android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_above="@+id/button_bar" - android:layout_alignParentTop="true" > + android:layout_alignParentTop="true"> <LinearLayout + android:id="@+id/account_main_layout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" > + android:orientation="vertical"> <RelativeLayout android:id="@+id/editor" android:layout_width="fill_parent" android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:background="@drawable/infocard_border" android:orientation="vertical" android:padding="@dimen/infocard_padding"> - <ImageView android:id="@+id/avater" + <com.makeramen.roundedimageview.RoundedImageView + android:id="@+id/avater" android:layout_width="72dp" android:layout_height="72dp" android:layout_alignParentTop="true" android:layout_marginRight="16dp" - android:contentDescription="@string/account_image_description"/> + android:contentDescription="@string/account_image_description" + app:riv_corner_radius="2dp"/> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" - android:orientation="vertical" android:layout_toRightOf="@+id/avater" + android:orientation="vertical" android:id="@+id/editAccountBoxes"> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/account_settings_jabber_id" - android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + <TextView + android:id="@+id/account_jid_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/account_settings_jabber_id" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> - <AutoCompleteTextView - android:id="@+id/account_jid" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/account_settings_example_jabber_id" - android:inputType="textEmailAddress" - android:textColor="@color/primaryText" - android:textColorHint="@color/secondaryText" - android:textSize="?attr/TextSizeBody" /> + <AutoCompleteTextView + android:id="@+id/account_jid" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/account_settings_example_jabber_id" + android:inputType="textEmailAddress" + android:textColor="@color/primaryText" + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody"/> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:text="@string/account_settings_password" - android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/account_settings_password" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> - <EditText - android:id="@+id/account_password" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="@string/password" - android:inputType="textPassword" - android:textColor="@color/primaryText" - android:textColorHint="@color/secondaryText" - android:textSize="?attr/TextSizeBody" /> + <EditText + android:id="@+id/account_password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/password" + android:inputType="textPassword" + android:textColor="@color/primaryText" + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody"/> - <CheckBox - android:id="@+id/account_register_new" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:text="@string/register_account" - android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + <LinearLayout + android:id="@+id/name_port" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:orientation="horizontal" + android:weightSum="1"> + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="0.8" + android:orientation="vertical"> + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/account_settings_hostname" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> + <EditText + android:id="@+id/hostname" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:hint="@string/hostname_example" + android:inputType="textNoSuggestions" + android:textColor="@color/primaryText" + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody"/> + </LinearLayout> + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="0.2" + android:orientation="vertical" + > + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/account_settings_port" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> + <EditText + android:id="@+id/port" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:inputType="number" + android:maxLength="5" + android:textColor="@color/primaryText" + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody"/> + </LinearLayout> + </LinearLayout> + <CheckBox + android:id="@+id/account_register_new" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/register_account" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> - <TextView - android:id="@+id/account_confirm_password_desc" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/account_settings_confirm_password" - android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" - android:visibility="gone" /> + <TextView + android:id="@+id/account_confirm_password_desc" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/account_settings_confirm_password" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody" + android:visibility="gone"/> - <EditText - android:id="@+id/account_password_confirm" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:hint="@string/confirm_password" - android:inputType="textPassword" - android:visibility="gone" - android:textColor="@color/primaryText" - android:textColorHint="@color/secondaryText" - android:textSize="?attr/TextSizeBody" /> + <EditText + android:id="@+id/account_password_confirm" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:hint="@string/confirm_password" + android:inputType="textPassword" + android:textColor="@color/primaryText" + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody" + android:visibility="gone"/> </LinearLayout> <LinearLayout android:layout_width="fill_parent" @@ -122,23 +179,66 @@ </LinearLayout> </RelativeLayout> - <LinearLayout + <RelativeLayout + android:id="@+id/battery_optimization" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/activity_vertical_margin" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:background="@drawable/infocard_border" + android:orientation="vertical" + android:padding="@dimen/infocard_padding" + android:visibility="gone"> + <TextView + android:id="@+id/batt_op_headline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/battery_optimizations_enabled" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeHeadline" + android:textStyle="bold"/> + <TextView + android:id="@+id/batt_op_body" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/batt_op_headline" + android:layout_marginBottom="8dp" + android:layout_marginTop="8dp" + android:text="@string/battery_optimizations_enabled_explained" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> + <Button + android:id="@+id/batt_op_disable" + style="?android:attr/borderlessButtonStyle" + android:layout_marginRight="-8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" android:layout_below="@+id/batt_op_body" + android:text="@string/disable" android:textColor="@color/accent"/> + </RelativeLayout> + + + <LinearLayout android:id="@+id/stats" android:layout_width="fill_parent" android:layout_height="fill_parent" + android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:background="@drawable/infocard_border" android:orientation="vertical" android:padding="@dimen/infocard_padding" - android:visibility="gone" > + android:visibility="gone"> <TableLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:stretchColumns="1" > + android:stretchColumns="1"> <TableRow android:layout_width="fill_parent" @@ -150,7 +250,7 @@ android:layout_height="wrap_content" android:text="@string/server_info_session_established" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/session_est" @@ -168,18 +268,18 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:stretchColumns="1" - android:visibility="gone" > + android:visibility="gone"> <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_pep" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_pep" @@ -193,14 +293,14 @@ <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_blocking" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_blocking" @@ -214,14 +314,14 @@ <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_stream_management" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_sm" @@ -235,14 +335,14 @@ <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_roster_version" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_roster_version" @@ -256,14 +356,14 @@ <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_carbon_messages" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_carbons" @@ -277,14 +377,14 @@ <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_mam" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_mam" @@ -298,14 +398,14 @@ <TableRow android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/server_info_csi" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <TextView android:id="@+id/server_info_csi" @@ -316,12 +416,52 @@ android:textSize="?attr/TextSizeBody" tools:ignore="RtlHardcoded"/> </TableRow> + <TableRow + android:id="@+id/push_row" + android:layout_width="fill_parent" + android:layout_height="wrap_content"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/server_info_push" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> + + <TextView + android:id="@+id/server_info_push" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> + </TableRow> + <TableRow + android:layout_width="fill_parent" + android:layout_height="wrap_content"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/server_info_http_upload" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> + + <TextView + android:id="@+id/server_info_http_upload" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody" + tools:ignore="RtlHardcoded"/> + </TableRow> </TableLayout> <RelativeLayout + android:id="@+id/otr_fingerprint_box" android:layout_width="wrap_content" android:layout_height="match_parent" - android:id="@+id/otr_fingerprint_box" android:layout_marginTop="32dp"> <LinearLayout @@ -335,16 +475,17 @@ android:id="@+id/otr_fingerprint" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:fontFamily="monospace" android:textColor="@color/primaryText" android:textSize="?attr/TextSizeBody" - android:typeface="monospace" /> + android:typeface="monospace"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" + android:text="@string/otr_fingerprint" android:textColor="@color/secondaryText" - android:textSize="?attr/TextSizeInfo" - android:text="@string/otr_fingerprint"/> + android:textSize="?attr/TextSizeInfo"/> </LinearLayout> <ImageButton @@ -354,12 +495,103 @@ android:layout_alignParentRight="true" android:layout_centerVertical="true" android:background="?android:selectableItemBackground" + android:contentDescription="@string/copy_otr_clipboard_description" android:padding="@dimen/image_button_padding" android:src="?attr/icon_copy" - android:visibility="visible" - android:contentDescription="@string/copy_otr_clipboard_description"/> + android:visibility="visible"/> + </RelativeLayout> + <RelativeLayout + android:id="@+id/axolotl_fingerprint_box" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginTop="32dp" + android:visibility="gone"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_toLeftOf="@+id/axolotl_actions" + android:orientation="vertical"> + + <TextView + android:id="@+id/axolotl_fingerprint" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="monospace" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody" + android:typeface="monospace"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/this_device_omemo_fingerprint" + android:textColor="@color/secondaryText" + android:textSize="?attr/TextSizeInfo"/> + </LinearLayout> + + <LinearLayout + android:id="@+id/axolotl_actions" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:orientation="vertical"> + + <ImageButton + android:id="@+id/action_copy_axolotl_to_clipboard" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="?android:selectableItemBackground" + android:contentDescription="@string/copy_omemo_clipboard_description" + android:padding="@dimen/image_button_padding" + android:src="?attr/icon_copy" + android:visibility="visible"/> + <ImageButton + android:id="@+id/action_regenerate_axolotl_key" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="?android:selectableItemBackground" + android:contentDescription="@string/regenerate_omemo_key" + android:padding="@dimen/image_button_padding" + android:src="?attr/icon_refresh" + android:visibility="gone"/> + + </LinearLayout> </RelativeLayout> </LinearLayout> + <LinearLayout + android:id="@+id/other_device_keys_card" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/activity_vertical_margin" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:background="@drawable/infocard_border" + android:orientation="vertical" + android:padding="@dimen/infocard_padding" + android:visibility="gone"> + + <TextView + android:id="@+id/other_device_keys_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/other_devices" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeHeadline" + android:textStyle="bold"/> + + <LinearLayout + android:id="@+id/other_device_keys" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:divider="?android:dividerHorizontal" + android:orientation="vertical" + android:showDividers="middle"> + </LinearLayout> + </LinearLayout> </LinearLayout> </ScrollView> @@ -368,10 +600,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" - android:layout_alignParentStart="true" android:layout_alignParentEnd="true" android:layout_alignParentLeft="true" - android:layout_alignParentRight="true" > + android:layout_alignParentRight="true" + android:layout_alignParentStart="true"> <Button android:id="@+id/cancel_button" @@ -380,14 +612,14 @@ android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/cancel" - android:textColor="@color/primaryText" /> + android:textColor="@color/primaryText"/> <View android:layout_width="1dp" android:layout_height="fill_parent" android:layout_marginBottom="7dp" android:layout_marginTop="7dp" - android:background="@color/black12" /> + android:background="@color/black12"/> <Button android:id="@+id/save_button" @@ -397,7 +629,7 @@ android:layout_weight="1" android:enabled="false" android:text="@string/save" - android:textColor="@color/secondaryText" /> + android:textColor="@color/secondaryText"/> </LinearLayout> </RelativeLayout> diff --git a/src/main/res/layout/activity_muc_details.xml b/src/main/res/layout/activity_muc_details.xml index a35e3301..4c6b0301 100644 --- a/src/main/res/layout/activity_muc_details.xml +++ b/src/main/res/layout/activity_muc_details.xml @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:background="@color/grey200"> + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="@color/grey200"> <LinearLayout + android:id="@+id/muc_main_layout" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical"> @@ -35,13 +37,13 @@ android:layout_height="wrap_content" android:layout_marginBottom="32dp"> - <ImageView + <com.makeramen.roundedimageview.RoundedImageView android:id="@+id/your_photo" android:layout_width="48dp" android:layout_height="48dp" android:layout_alignParentLeft="true" - android:src="@drawable/ic_profile"> - </ImageView> + android:src="@drawable/ic_profile" + app:riv_corner_radius="2dp" /> <LinearLayout android:layout_width="fill_parent" @@ -86,7 +88,7 @@ android:id="@+id/muc_conference_type" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/private_conference" + android:text="@string/private_conference" android:layout_centerVertical="true" android:textColor="@color/primaryText" android:textSize="?attr/TextSizeBody" @@ -102,10 +104,66 @@ android:layout_alignParentRight="true" android:layout_centerVertical="true" android:background="?android:selectableItemBackground" - android:padding="@dimen/image_button_padding" + android:padding="8dp" android:src="?attr/icon_settings"/> </RelativeLayout> + <RelativeLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content"> + <TextView + android:id="@+id/notification_status_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/notify_on_all_messages" + android:layout_centerVertical="true" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody" + android:layout_alignParentLeft="true" + android:layout_toLeftOf="@+id/notification_status_button" + /> + <ImageButton + android:id="@+id/notification_status_button" + style="?android:attr/buttonStyleSmall" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:background="?android:selectableItemBackground" + android:padding="@dimen/image_button_padding" + android:src="@drawable/ic_notifications_grey600_24dp"/> + </RelativeLayout> + + <TableLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/muc_info_more" + android:stretchColumns="1" + android:visibility="gone"> + + <TableRow + android:layout_width="fill_parent" + android:layout_height="match_parent"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/server_info_mam" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody" /> + + <TextView + android:id="@+id/muc_info_mam" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody" /> + </TableRow> + + </TableLayout> + <TextView android:id="@+id/details_account" android:layout_width="wrap_content" diff --git a/src/main/res/layout/activity_publish_profile_picture.xml b/src/main/res/layout/activity_publish_profile_picture.xml index 45f979e3..1520b330 100644 --- a/src/main/res/layout/activity_publish_profile_picture.xml +++ b/src/main/res/layout/activity_publish_profile_picture.xml @@ -12,12 +12,12 @@ android:layout_centerHorizontal="true" android:layout_marginBottom="8dp" android:layout_marginTop="24dp" - android:background="@drawable/message_border" > + android:background="@drawable/account_image_border" > <ImageView android:id="@+id/account_image" - android:layout_width="194dp" - android:layout_height="194dp" /> + android:layout_width="192dp" + android:layout_height="192dp" /> </LinearLayout> <TextView diff --git a/src/main/res/layout/activity_trust_keys.xml b/src/main/res/layout/activity_trust_keys.xml new file mode 100644 index 00000000..6f8a1bc9 --- /dev/null +++ b/src/main/res/layout/activity_trust_keys.xml @@ -0,0 +1,152 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/grey200" > + <ScrollView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_above="@+id/button_bar" + android:layout_alignParentTop="true" > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/key_error_message_card" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" + android:background="@drawable/infocard_border" + android:orientation="vertical" + android:padding="@dimen/infocard_padding" + android:visibility="gone"> + + <TextView + android:id="@+id/key_error_message_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeHeadline" + android:textStyle="bold" + android:text="@string/error_trustkeys_title"/> + + <TextView + android:id="@+id/key_error_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody" + android:padding="8dp"/> + + </LinearLayout> + + <LinearLayout + android:id="@+id/own_keys_card" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" + android:background="@drawable/infocard_border" + android:orientation="vertical" + android:padding="@dimen/infocard_padding" + android:visibility="gone"> + + <TextView + android:id="@+id/own_keys_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeHeadline" + android:textStyle="bold"/> + + <LinearLayout + android:id="@+id/own_keys_details" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:divider="?android:dividerHorizontal" + android:showDividers="middle" + android:orientation="vertical"> + </LinearLayout> + + </LinearLayout> + + <LinearLayout + android:id="@+id/foreign_keys_card" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" + android:background="@drawable/infocard_border" + android:orientation="vertical" + android:padding="@dimen/infocard_padding" + android:visibility="gone"> + + <TextView + android:id="@+id/foreign_keys_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeHeadline" + android:textStyle="bold"/> + + <LinearLayout + android:id="@+id/foreign_keys_details" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:divider="?android:dividerHorizontal" + android:showDividers="middle" + android:orientation="vertical"> + </LinearLayout> + + </LinearLayout> + + </LinearLayout> + </ScrollView> + <LinearLayout + android:id="@+id/button_bar" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentStart="true" + android:layout_alignParentEnd="true" + android:layout_alignParentLeft="true" + android:layout_alignParentRight="true" > + + <Button + android:id="@+id/cancel_button" + style="?android:attr/borderlessButtonStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/cancel" + android:textColor="@color/black87" /> + + <View + android:layout_width="1dp" + android:layout_height="fill_parent" + android:layout_marginBottom="7dp" + android:layout_marginTop="7dp" + android:background="@color/black12" /> + + <Button + android:id="@+id/save_button" + style="?android:attr/borderlessButtonStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:enabled="true" + android:textColor="@color/black54" + android:text="@string/done"/> + </LinearLayout> +</RelativeLayout> diff --git a/src/main/res/layout/activity_verify_otr.xml b/src/main/res/layout/activity_verify_otr.xml index ce8b831e..2d5821a4 100644 --- a/src/main/res/layout/activity_verify_otr.xml +++ b/src/main/res/layout/activity_verify_otr.xml @@ -36,7 +36,8 @@ android:layout_height="wrap_content" android:textColor="@color/primaryText" android:textSize="?attr/TextSizeBody" - android:typeface="monospace"/> + android:typeface="monospace" + android:fontFamily="monospace"/> <TextView android:layout_width="wrap_content" @@ -52,7 +53,8 @@ android:layout_marginTop="20dp" android:textColor="@color/primaryText" android:textSize="?attr/TextSizeBody" - android:typeface="monospace"/> + android:typeface="monospace" + android:fontFamily="monospace"/> <TextView android:layout_width="wrap_content" diff --git a/src/main/res/layout/certificate_information.xml b/src/main/res/layout/certificate_information.xml new file mode 100644 index 00000000..4c085459 --- /dev/null +++ b/src/main/res/layout/certificate_information.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="16dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_subject" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeHeadline"/> + <TextView + android:layout_marginTop="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_cn" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/subject_cn" + android:textColor="@color/black54" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_marginTop="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_o" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/subject_o" + android:textColor="@color/black54" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_marginTop="16dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_issuer" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeHeadline"/> + <TextView + android:layout_marginTop="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_cn" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/issuer_cn" + android:textColor="@color/black54" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_marginTop="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_o" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/issuer_o" + android:textColor="@color/black54" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_marginTop="16dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/certificate_sha1" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/sha1" + android:textColor="@color/black54" + android:textSize="?attr/TextSizeBody" + android:typeface="monospace" + android:fontFamily="monospace"/> +</LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/contact.xml b/src/main/res/layout/contact.xml index 853387f3..faf017d0 100644 --- a/src/main/res/layout/contact.xml +++ b/src/main/res/layout/contact.xml @@ -1,17 +1,19 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?android:attr/activatedBackgroundIndicator" - android:padding="8dp" > + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:attr/activatedBackgroundIndicator" + android:padding="8dp"> - <ImageView + <com.makeramen.roundedimageview.RoundedImageView android:id="@+id/contact_photo" android:layout_width="48dp" android:layout_height="48dp" android:layout_alignParentLeft="true" android:scaleType="centerCrop" - android:src="@drawable/ic_profile" /> + android:src="@drawable/ic_profile" + app:riv_corner_radius="2dp" /> <TextView android:layout_width="48dp" android:layout_height="4dp" @@ -58,6 +60,7 @@ android:textColor="@color/primaryText" android:textSize="?attr/TextSizeHeadline" android:typeface="monospace" + android:fontFamily="monospace" android:visibility="gone" /> </LinearLayout> diff --git a/src/main/res/layout/contact_key.xml b/src/main/res/layout/contact_key.xml index 22e4e576..6882abc4 100644 --- a/src/main/res/layout/contact_key.xml +++ b/src/main/res/layout/contact_key.xml @@ -3,39 +3,67 @@ android:layout_width="wrap_content" android:layout_height="match_parent" > - <LinearLayout + <RelativeLayout + android:id="@+id/key_data" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" - android:layout_toLeftOf="@+id/button_remove" - android:orientation="vertical" - android:padding="8dp" > + android:paddingTop="8dp" + android:paddingLeft="8dp" + android:paddingBottom="8dp"> <TextView android:id="@+id/key" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/primaryText" + android:layout_alignParentLeft="true" + android:layout_toLeftOf="@+id/tgl_trust" android:textSize="?attr/TextSizeBody" - android:typeface="monospace" /> + android:typeface="monospace" + android:fontFamily="monospace"/> <TextView android:id="@+id/key_type" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/secondaryText" + android:layout_alignParentLeft="true" + android:layout_below="@+id/key" + android:maxLines="1" + android:textSize="?attr/TextSizeInfo"/> + + <TextView + android:id="@+id/key_trust" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/key" + android:visibility="gone" + android:textColor="@color/secondaryText" android:textSize="?attr/TextSizeInfo"/> - </LinearLayout> <ImageButton android:id="@+id/button_remove" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" + android:layout_toRightOf="@+id/key" android:layout_centerVertical="true" android:background="?android:selectableItemBackground" android:padding="@dimen/image_button_padding" android:src="?attr/icon_remove" - android:visibility="invisible" /> + android:visibility="gone" /> + + + <de.thedevstack.conversationsplus.ui.widget.Switch + android:id="@+id/tgl_trust" + android:visibility="invisible" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + style="@style/MD"/> + </RelativeLayout> </RelativeLayout>
\ No newline at end of file diff --git a/src/main/res/layout/conversation_list_row.xml b/src/main/res/layout/conversation_list_row.xml index 4ac7440c..9f040676 100644 --- a/src/main/res/layout/conversation_list_row.xml +++ b/src/main/res/layout/conversation_list_row.xml @@ -1,6 +1,6 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:emojicon="http://schemas.android.com/apk/res-auto" + xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" @@ -9,30 +9,32 @@ android:id="@+id/conversationListRowFrame" android:background="@color/primaryBackground"> - <View - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:background="@color/red500"/> + <View + android:layout_width="fill_parent" + android:layout_height="match_parent" + android:background="@color/red500"/> - <FrameLayout - android:id="@+id/swipeable_item" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:background="@color/grey50"> + <FrameLayout + android:id="@+id/swipeable_item" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:background="@color/grey50"> - <RelativeLayout - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:padding="8dp" + <RelativeLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:background="?android:selectableItemBackground" + android:orientation="horizontal" + android:padding="8dp" android:id="@+id/conversationListRowContent"> - <ImageView - android:id="@+id/conversation_image" - android:layout_width="56dp" - android:layout_height="56dp" - android:layout_alignParentLeft="true" - android:scaleType="centerCrop" /> + <com.makeramen.roundedimageview.RoundedImageView + android:id="@+id/conversation_image" + android:layout_width="56dp" + android:layout_height="56dp" + android:layout_alignParentLeft="true" + android:scaleType="centerCrop" + app:riv_corner_radius="2dp"/> <TextView android:layout_width="56dp" @@ -43,61 +45,82 @@ android:paddingTop="8dp" android:paddingRight="8dp"/> - <RelativeLayout - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:layout_toRightOf="@+id/conversation_image" - android:paddingLeft="8dp"> + <RelativeLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toRightOf="@+id/conversation_image" + android:paddingLeft="8dp"> + <TextView + android:id="@+id/conversation_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignLeft="@+id/conversation_lastwrapper" + android:layout_toLeftOf="@+id/conversation_lastupdate" + android:paddingRight="4dp" + android:singleLine="true" + android:text="Awesome groupchat" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeHeadline" + android:typeface="sans"/> - <TextView - android:id="@+id/conversation_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignLeft="@+id/conversation_lastwrapper" - android:layout_toLeftOf="@+id/conversation_lastupdate" - android:singleLine="true" - android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeHeadline" - android:typeface="sans" /> + <RelativeLayout + android:id="@+id/conversation_lastwrapper" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/conversation_name" + android:layout_marginTop="4dp"> - <LinearLayout - android:id="@+id/conversation_lastwrapper" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_below="@id/conversation_name" - android:orientation="vertical" - android:paddingTop="3dp" > + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_centerVertical="true" + android:layout_toLeftOf="@+id/notification_status" + android:orientation="vertical"> <github.ankushsachdeva.emojicon.EmojiconTextView android:id="@+id/conversation_lastmsg" - android:layout_width="fill_parent" + android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollHorizontally="false" android:singleLine="true" + android:text="This is a placeholder text to show the last messages" android:textColor="@color/primaryText" android:textSize="?attr/TextSizeBody" - emojicon:emojiconSize="20sp" /> + app:emojiconSize="20sp" /> - <ImageView - android:id="@+id/conversation_lastimage" - android:layout_width="fill_parent" - android:layout_height="36dp" - android:background="@color/primaryText" - android:scaleType="centerCrop" /> - </LinearLayout> + <com.makeramen.roundedimageview.RoundedImageView + android:id="@+id/conversation_lastimage" + android:layout_width="fill_parent" + android:layout_height="36dp" + android:background="@color/primaryText" + android:scaleType="centerCrop" + android:visibility="gone" + app:riv_corner_radius="2dp"/> + </LinearLayout> + <ImageView + android:id="@+id/notification_status" + android:layout_width="16sp" + android:layout_height="16sp" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:layout_marginLeft="4dp" + android:src="@drawable/ic_notifications_grey600_24dp" + /> + </RelativeLayout> - <TextView - android:id="@+id/conversation_lastupdate" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignBaseline="@+id/conversation_name" - android:layout_alignParentRight="true" - android:gravity="right" - android:textColor="@color/secondaryText" - android:textSize="?attr/TextSizeInfo" /> - </RelativeLayout> - </RelativeLayout> - </FrameLayout> -</FrameLayout> + <TextView + android:id="@+id/conversation_lastupdate" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignBaseline="@+id/conversation_name" + android:layout_alignParentRight="true" + android:gravity="right" + android:text="23:42" + android:textColor="@color/secondaryText" + android:textSize="?attr/TextSizeInfo"/> + </RelativeLayout> + </RelativeLayout> + </FrameLayout> +</FrameLayout>
\ No newline at end of file diff --git a/src/main/res/layout/create_contact_dialog.xml b/src/main/res/layout/enter_jid_dialog.xml index d4fba603..f75f2674 100644 --- a/src/main/res/layout/create_contact_dialog.xml +++ b/src/main/res/layout/enter_jid_dialog.xml @@ -1,9 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:padding="8dp" > + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingBottom="8dp" + android:paddingLeft="24dp" + android:paddingRight="24dp" + android:paddingTop="16dp"> <TextView android:id="@+id/your_account" @@ -11,12 +14,12 @@ android:layout_height="wrap_content" android:text="@string/your_account" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <Spinner android:id="@+id/account" android:layout_width="fill_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content"/> <TextView android:id="@+id/jabber_id" @@ -25,7 +28,7 @@ android:layout_marginTop="8dp" android:text="@string/account_settings_jabber_id" android:textColor="@color/primaryText" - android:textSize="?attr/TextSizeBody" /> + android:textSize="?attr/TextSizeBody"/> <AutoCompleteTextView android:id="@+id/jid" @@ -34,6 +37,7 @@ android:hint="@string/account_settings_example_jabber_id" android:inputType="textEmailAddress" android:textColor="@color/primaryText" - android:textColorHint="@color/secondaryText" /> + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody" /> </LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/form_boolean.xml b/src/main/res/layout/form_boolean.xml new file mode 100644 index 00000000..fd553acb --- /dev/null +++ b/src/main/res/layout/form_boolean.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="8dp" + android:orientation="vertical"> + <CheckBox + android:id="@+id/field" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> +</LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/form_text.xml b/src/main/res/layout/form_text.xml new file mode 100644 index 00000000..31b521e8 --- /dev/null +++ b/src/main/res/layout/form_text.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="8dp" + android:orientation="vertical"> + <TextView + android:id="@+id/label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textSize="?attr/TextSizeBody"/> + + <EditText + android:id="@+id/field" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textColor="@color/black87" + android:textColorHint="@color/black54" + android:textSize="?attr/TextSizeBody"/> +</LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml index e70e9a05..c3a86616 100644 --- a/src/main/res/layout/fragment_conversation.xml +++ b/src/main/res/layout/fragment_conversation.xml @@ -4,7 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/grey200"> + android:background="@color/grey200" > <com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout android:layout_width="fill_parent" @@ -27,7 +27,7 @@ android:listSelector="@android:color/transparent" android:stackFromBottom="true" android:transcriptMode="normal" - tools:listitem="@layout/message_sent" > + tools:listitem="@layout/message_sent"> </ListView> </com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout> diff --git a/src/main/res/layout/join_conference_dialog.xml b/src/main/res/layout/join_conference_dialog.xml index ef113017..d172142a 100644 --- a/src/main/res/layout/join_conference_dialog.xml +++ b/src/main/res/layout/join_conference_dialog.xml @@ -1,9 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:padding="8dp" > + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingBottom="8dp" + android:paddingLeft="24dp" + android:paddingRight="24dp" + android:paddingTop="16dp"> <TextView android:id="@+id/your_account" @@ -34,7 +37,8 @@ android:hint="@string/conference_address_example" android:inputType="textEmailAddress" android:textColor="@color/primaryText" - android:textColorHint="@color/secondaryText" /> + android:textColorHint="@color/secondaryText" + android:textSize="?attr/TextSizeBody"/> <CheckBox android:id="@+id/bookmark" @@ -42,6 +46,8 @@ android:layout_height="wrap_content" android:layout_marginTop="8dp" android:checked="true" - android:text="@string/save_as_bookmark" /> + android:text="@string/save_as_bookmark" + android:textColor="@color/primaryText" + android:textSize="?attr/TextSizeBody"/> </LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/message_received.xml b/src/main/res/layout/message_received.xml index f16f551a..93ab095b 100644 --- a/src/main/res/layout/message_received.xml +++ b/src/main/res/layout/message_received.xml @@ -1,14 +1,23 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:emojicon="http://schemas.android.com/apk/res-auto" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingBottom="4dp" - android:paddingLeft="8dp" - android:paddingRight="8dp" - android:paddingTop="4dp" > +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="3dp" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:paddingTop="3dp"> + + <com.makeramen.roundedimageview.RoundedImageView + android:id="@+id/message_photo" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_alignParentLeft="true" + android:layout_alignParentTop="true" + android:scaleType="fitXY" + android:src="@drawable/ic_profile" + app:riv_corner_radius="2dp" /> <LinearLayout android:id="@+id/message_box" @@ -16,28 +25,28 @@ android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_toRightOf="@+id/message_photo" - android:background="@drawable/message_border" - android:minHeight="48dp" + android:background="@drawable/message_bubble_received_white" + android:minHeight="53dp" + android:layout_marginTop="-2dp" + android:layout_marginRight="-4dp" android:longClickable="true"> <LinearLayout android:layout_width="wrap_content" android:layout_height="fill_parent" - android:background="@color/grey50" + android:background="@color/primaryBackground" android:gravity="center_vertical" android:orientation="vertical" - android:paddingBottom="4dp" - android:paddingLeft="5dp" - android:paddingRight="5dp" - android:paddingTop="4dp" > + android:padding="2dp"> <ImageView android:id="@+id/message_image" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginBottom="4dp" android:adjustViewBounds="true" - android:background="@color/primaryText" - android:paddingBottom="2dp" + android:background="@color/black87" android:scaleType="centerCrop" /> <github.ankushsachdeva.emojicon.EmojiconTextView @@ -45,9 +54,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:autoLink="web|phone|email" + android:textColorLink="@color/primaryText" android:textColor="@color/primaryText" + android:textColorHighlight="@color/grey500" android:textSize="?attr/TextSizeBody" - emojicon:emojiconSize="28sp" /> + app:emojiconSize="28sp" /> <Button android:id="@+id/download_button" @@ -59,8 +70,20 @@ <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_gravity="left" android:orientation="horizontal" - android:paddingTop="1dp" > + android:paddingBottom="2dp"> + + <TextView + android:id="@+id/message_encryption" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:gravity="center_vertical" + android:layout_marginRight="4sp" + android:textColor="@color/white70" + android:textStyle="bold" + android:textSize="?attr/TextSizeInfo" /> <ImageView android:id="@+id/security_indicator" @@ -68,9 +91,9 @@ android:layout_height="?attr/TextSizeInfo" android:layout_gravity="center_vertical" android:layout_marginRight="4sp" - android:alpha="0.54" + android:alpha="0.70" android:gravity="center_vertical" - android:src="@drawable/ic_secure_indicator" /> + android:src="@drawable/ic_lock_white_18dp" /> <TextView android:id="@+id/message_time" @@ -79,21 +102,10 @@ android:layout_gravity="center_vertical" android:gravity="center_vertical" android:text="@string/sending" - android:textColor="@color/secondaryText" + android:textColor="@color/white70" android:textSize="?attr/TextSizeInfo" /> </LinearLayout> </LinearLayout> </LinearLayout> - <ImageView - android:id="@+id/message_photo" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" - android:layout_marginRight="-1.5dp" - android:padding="0dp" - android:scaleType="fitXY" - android:src="@drawable/ic_profile" /> - </RelativeLayout>
\ No newline at end of file diff --git a/src/main/res/layout/message_sent.xml b/src/main/res/layout/message_sent.xml index 00368194..909ae9c7 100644 --- a/src/main/res/layout/message_sent.xml +++ b/src/main/res/layout/message_sent.xml @@ -1,14 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:emojicon="http://schemas.android.com/apk/res-auto" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingBottom="4dp" - android:paddingLeft="8dp" - android:paddingRight="8dp" - android:paddingTop="4dp" > +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="3dp" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:paddingTop="3dp"> + + <com.makeramen.roundedimageview.RoundedImageView + android:id="@+id/message_photo" + android:layout_width="48dp" + android:layout_height="48dp" + android:scaleType="fitXY" + android:paddingBottom="3dp" + android:src="@drawable/ic_profile" + android:layout_alignParentBottom="true" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + app:riv_corner_radius="2dp" /> <LinearLayout android:id="@+id/message_box" @@ -16,28 +27,26 @@ android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_toLeftOf="@+id/message_photo" - android:background="@drawable/message_border" - android:minHeight="48dp" + android:background="@drawable/message_bubble_sent" + android:minHeight="53dp" + android:layout_marginLeft="-4dp" android:longClickable="true"> <LinearLayout android:layout_width="wrap_content" android:layout_height="fill_parent" - android:background="@color/grey50" android:gravity="center_vertical" android:orientation="vertical" - android:paddingBottom="4dp" - android:paddingLeft="5dp" - android:paddingRight="5dp" - android:paddingTop="4dp" > + android:padding="2dp"> <ImageView android:id="@+id/message_image" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginBottom="4dp" android:adjustViewBounds="true" android:background="@color/primaryText" - android:paddingBottom="2dp" android:scaleType="centerCrop" /> <github.ankushsachdeva.emojicon.EmojiconTextView @@ -45,11 +54,13 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:autoLink="web|phone|email" + android:textColorLink="@color/primaryText" android:textColor="@color/primaryText" + android:textColorHighlight="@color/grey500" android:textSize="?attr/TextSizeBody" - emojicon:emojiconSize="28sp" /> - - <Button + app:emojiconSize="28sp"/> + + <Button android:id="@+id/download_button" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" @@ -61,7 +72,7 @@ android:layout_height="wrap_content" android:layout_gravity="right" android:orientation="horizontal" - android:paddingTop="1dp" > + android:paddingBottom="2dp"> <TextView android:id="@+id/message_time" @@ -81,7 +92,7 @@ android:layout_marginLeft="4sp" android:alpha="0.54" android:gravity="center_vertical" - android:src="@drawable/ic_secure_indicator" /> + android:src="@drawable/ic_lock_black_18dp" /> <ImageView android:id="@+id/indicator_received" @@ -96,15 +107,4 @@ </LinearLayout> </LinearLayout> - <ImageView - android:id="@+id/message_photo" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_alignParentBottom="true" - android:layout_alignParentRight="true" - android:layout_marginLeft="-1.5dp" - android:padding="0dp" - android:scaleType="fitXY" - android:src="@drawable/ic_profile" /> - </RelativeLayout>
\ No newline at end of file diff --git a/src/main/res/layout/message_status.xml b/src/main/res/layout/message_status.xml index 412fbde0..aa02e154 100644 --- a/src/main/res/layout/message_status.xml +++ b/src/main/res/layout/message_status.xml @@ -1,14 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingBottom="6dp" - android:paddingLeft="8dp" - android:paddingRight="6dp" - android:paddingTop="6dp" > + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="5dp" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:paddingTop="5dp"> - <ImageView + <com.makeramen.roundedimageview.RoundedImageView android:id="@+id/message_photo" android:layout_width="32dp" android:layout_height="32dp" @@ -17,7 +18,8 @@ android:layout_marginRight="-1.5dp" android:padding="0dp" android:scaleType="fitXY" - android:src="@drawable/ic_profile" /> + android:src="@drawable/ic_profile" + app:riv_corner_radius="1dp"/> <TextView android:id="@+id/status_message" diff --git a/src/main/res/menu/choose_contact.xml b/src/main/res/menu/choose_contact.xml index c025d00f..94b6479b 100644 --- a/src/main/res/menu/choose_contact.xml +++ b/src/main/res/menu/choose_contact.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android" > +<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/action_search" @@ -8,4 +8,10 @@ android:showAsAction="collapseActionView|always" android:title="@string/search"/> -</menu>
\ No newline at end of file + <item + android:id="@+id/action_create_contact" + android:icon="?attr/icon_add_person" + android:showAsAction="always" + android:title="@string/create_contact" + android:visible="false"/> +</menu> diff --git a/src/main/res/menu/editaccount.xml b/src/main/res/menu/editaccount.xml index 4ce9e1f3..9f06fc4c 100644 --- a/src/main/res/menu/editaccount.xml +++ b/src/main/res/menu/editaccount.xml @@ -2,22 +2,43 @@ <item android:id="@+id/action_show_qr_code" - android:title="@string/show_qr_code" - android:showAsAction="never" /> + android:showAsAction="never" + android:title="@string/show_qr_code"/> <item android:id="@+id/action_show_block_list" - android:title="@string/show_block_list" - android:showAsAction="never" /> + android:showAsAction="never" + android:title="@string/show_block_list"/> + + <item + android:id="@+id/action_renew_certificate" + android:showAsAction="never" + android:title="@string/action_renew_certificate" + android:visible="false"/> <item android:id="@+id/action_server_info_show_more" - android:title="@string/server_info_show_more" android:checkable="true" android:checked="false" - android:showAsAction="never" /> + android:showAsAction="never" + android:title="@string/server_info_show_more"/> + + <item + android:id="@+id/action_mam_prefs" + android:title="@string/mam_prefs"/> - <item android:id="@+id/action_change_password_on_server" - android:title="@string/change_password" - android:showAsAction="never" /> + <item + android:id="@+id/action_change_password_on_server" + android:showAsAction="never" + android:title="@string/change_password"/> + + <item + android:id="@+id/action_clear_devices" + android:showAsAction="never" + android:title="@string/clear_other_devices"/> + <item + android:id="@+id/action_settings" + android:orderInCategory="100" + android:showAsAction="never" + android:title="@string/action_settings"/> </menu>
\ No newline at end of file diff --git a/src/main/res/menu/encryption_choices.xml b/src/main/res/menu/encryption_choices.xml index adf0ad8d..445c4114 100644 --- a/src/main/res/menu/encryption_choices.xml +++ b/src/main/res/menu/encryption_choices.xml @@ -4,7 +4,11 @@ <group android:checkableBehavior="single" > <item android:id="@+id/encryption_choice_none" - android:title="@string/encryption_choice_none"/> + android:title="@string/encryption_choice_unencrypted"/> + <item + android:id="@+id/encryption_choice_axolotl" + android:title="@string/encryption_choice_omemo" + android:visible="@bool/omemo_enabled"/> <item android:id="@+id/encryption_choice_otr" android:title="@string/encryption_choice_otr"/> diff --git a/src/main/res/menu/manageaccounts.xml b/src/main/res/menu/manageaccounts.xml index f8a30ff7..ffa692a0 100644 --- a/src/main/res/menu/manageaccounts.xml +++ b/src/main/res/menu/manageaccounts.xml @@ -7,6 +7,12 @@ android:showAsAction="always" android:title="@string/action_add_account"/> <item + android:id="@+id/action_add_account_with_cert" + android:showAsAction="never" + android:icon="?attr/icon_add_person" + android:title="@string/action_add_account_with_certificate" + android:visible="true"/> + <item android:id="@+id/action_enable_all" android:title="@string/enable_all_accounts"/> <item diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml index 2fe1ca22..292a4938 100644 --- a/src/main/res/menu/message_context.xml +++ b/src/main/res/menu/message_context.xml @@ -9,6 +9,10 @@ android:title="@string/copy_text" android:visible="false"/> <item + android:id="@+id/retry_decryption" + android:title="@string/retry_decryption" + android:visible="false"/> + <item android:id="@+id/share_with" android:title="@string/share_with" android:visible="false"/> diff --git a/src/main/res/menu/muc_details_context.xml b/src/main/res/menu/muc_details_context.xml index dc0f5d3e..af5e691b 100644 --- a/src/main/res/menu/muc_details_context.xml +++ b/src/main/res/menu/muc_details_context.xml @@ -3,30 +3,38 @@ <item android:id="@+id/start_conversation" android:title="@string/start_conversation" - android:visible="false" /> - <item - android:id="@+id/give_membership" + android:visible="false"/> + <item + android:id="@+id/action_contact_details" + android:title="@string/action_contact_details" + android:visible="false"/> + <item + android:id="@+id/send_private_message" + android:title="@string/send_private_message" + android:visible="false"/> + <item + android:id="@+id/give_membership" android:title="@string/grant_membership" - android:visible="false" /> - <item - android:id="@+id/give_admin_privileges" + android:visible="false"/> + <item + android:id="@+id/give_admin_privileges" android:title="@string/grant_admin_privileges" - android:visible="false"/> - <item - android:id="@+id/remove_admin_privileges" + android:visible="false"/> + <item + android:id="@+id/remove_admin_privileges" android:title="@string/remove_admin_privileges" - android:visible="false"/> + android:visible="false"/> - <item - android:id="@+id/remove_membership" - android:title="@string/remove_membership" - android:visible="false"/> - <item - android:id="@+id/ban_from_conference" - android:title="@string/ban_from_conference" - android:visible="false" /> - <item - android:id="@+id/remove_from_room" + <item + android:id="@+id/remove_membership" + android:title="@string/remove_membership" + android:visible="false"/> + <item + android:id="@+id/ban_from_conference" + android:title="@string/ban_from_conference" + android:visible="false"/> + <item + android:id="@+id/remove_from_room" android:title="@string/remove_from_room" - android:visible="false"/> -</menu>
\ No newline at end of file + android:visible="false"/> +</menu> diff --git a/src/main/res/values-ar-rEG/strings.xml b/src/main/res/values-ar-rEG/strings.xml deleted file mode 100644 index 79590fd6..00000000 --- a/src/main/res/values-ar-rEG/strings.xml +++ /dev/null @@ -1,319 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<resources> - <string name="action_settings">إعدادات</string> - <string name="action_add">محادثة جديدة</string> - <string name="action_accounts">إدارة الحسابات</string> - <string name="action_end_conversation">إنهاء المحادثة</string> - <string name="action_contact_details">بيانات جهة الإتصال</string> - <string name="action_muc_details">بيانات الغرفة</string> - <string name="action_secure">تشفير المحادثة</string> - <string name="action_add_account">إضافة حساب</string> - <string name="action_edit_contact">تعديل الإسم</string> - <string name="action_add_phone_book">إضافة لسجل الهاتف</string> - <string name="action_delete_contact">حذف من الإضافات</string> - <string name="action_block_contact">حجب جهة إتصال</string> - <string name="action_unblock_contact">إنهاء حجب جهة اتصال</string> - <string name="action_block_domain">حجب دومين</string> - <string name="action_unblock_domain">إنهاء حجب دومين</string> - <string name="title_activity_manage_accounts">إدارة الحسابات</string> - <string name="title_activity_settings">إعدادات</string> - <string name="title_activity_conference_details">بيانات الغرفه</string> - <string name="title_activity_contact_details">بيانات جهة الإتصال</string> - <string name="title_activity_sharewith">مشاركة مع محادثة</string> - <string name="title_activity_start_conversation">بدأ محادثة</string> - <string name="title_activity_choose_contact">إختيار جهة اتصال</string> - <string name="title_activity_block_list">قائمة المحجوبين</string> - <string name="just_now">الآن</string> - <string name="minute_ago">منذ 1 دقيقة</string> - <string name="minutes_ago">دقائق %d منذ</string> - <string name="unread_conversations">محادثات غير مقروءة</string> - <string name="sending">ارسال</string> - <string name="encrypted_message">رسالة مشفّرة .. الرجاء الإنتظار</string> - <string name="nick_in_use">اللقب مستخدم من قبل</string> - <string name="admin">مدير</string> - <string name="owner">مالك</string> - <string name="moderator">مشرف</string> - <string name="participant">مشترك</string> - <string name="visitor">زائر</string> - <string name="remove_contact_text">هل ترغب بحذف جهة الإتصال %s من اضافاتك? ستبقى محادثاتك معه محفوظه</string> - <string name="block_contact_text">هل ترغب في حجب %s من ارسال الرسائل لك?</string> - <string name="unblock_contact_text">هل ترغب في انهاء حجب %s والسماح له بمراسلتك?</string> - <string name="block_domain_text">هل تريد حجب جميع جهات الإتصال من %s?</string> - <string name="unblock_domain_text">الغاء حجب جميع جهات الإتصال من %s?</string> - <string name="contact_blocked">جهة الاتصال محجوبه</string> - <string name="remove_bookmark_text">هل ترغب في حذف %s من المفضلات? المحادثات المحفوظه ستبقى كما هي</string> - <string name="register_account">تسجيل حساب جديد في سيرفر</string> - <string name="change_password_on_server">تغيير كلمة المرور في سيرفر</string> - <string name="share_with">مشاركة مع</string> - <string name="start_conversation">بدأ محادثة</string> - <string name="invite_contact">دعوة صديق</string> - <string name="contacts">جهات الإتصال</string> - <string name="cancel">الغاء</string> - <string name="set">تعيين</string> - <string name="add">اضافة</string> - <string name="edit">تعديل</string> - <string name="delete">حذف</string> - <string name="block">حجب</string> - <string name="unblock">الغاء حجب</string> - <string name="save">حفظ</string> - <string name="ok">موافق</string> - <string name="crash_report_title">توقف التطبيق عن العمل</string> - <string name="crash_report_message">بموافقتك على ارسال تقارير الأخطاء ستساعد مبرمجي التطبيق على تحسين جودة التطبيق\n<b>تنويه:</b> سيرسل تقارير الأخطاء ان وجدت بأحد الحسابات التي أضفتها.</string> - <string name="send_now">ارسال الآن</string> - <string name="send_never">لا تسألني ثانية</string> - <string name="problem_connecting_to_account">لا يمكن تسجيل الدخول لحسابك</string> - <string name="problem_connecting_to_accounts">لا يمكن تسجيل الدخول بحساباتك</string> - <string name="touch_to_fix">المس الشاشه لعرض المحادثات</string> - <string name="attach_file">ارفاق ملف</string> - <string name="not_in_roster">جهة الاتصال ليست مضافه لديك هل ترغب في إضافتها ؟؟</string> - <string name="add_contact">اضافة جهة اتصال</string> - <string name="send_failed">فشل التسليم</string> - <string name="send_rejected">مرفوض</string> - <string name="preparing_image">اعداد صورة للإرسال</string> - <string name="action_clear_history">حذف سجل المحفوظات</string> - <string name="clear_conversation_history">حذف سجل المحفوظات للمحادثة</string> - <string name="clear_histor_msg">هل ترغب بحذف جميع الرسائل في تلك المحادثة?\n\n<b>تنويه:</b> هذا لن يؤثر على الرسائل المخزنة على الأجهزة أو أي أماكن أخرى.</string> - <string name="delete_messages">حذف الرسائل</string> - <string name="also_end_conversation">انهاء هذه المحادثة بعد الكلمات</string> - <string name="choose_presence">اختيار ظهورك لجهات الإتصال</string> - <string name="send_plain_text_message">ارسال رسالة غير مشفّرة</string> - <string name="send_otr_message">OTRارساله رساله مشفره عبر</string> - <string name="send_pgp_message">OpenPGPارساله رساله مشفره عبر</string> - <string name="your_nick_has_been_changed">تم تغيير لقبك بنجاح</string> - <string name="send_unencrypted">إرسال بدون تشفير</string> - <string name="decryption_failed">فشل فك التشفير. ربما لم يكن لديك المفتاح الخاص الصحيح.</string> - <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Conversations :: يستخدم تطبيق آخر يسمى <b> OpenKeychain </b> لتشفير وفك تشفير الرسائل وإدارة المفاتيح العامة الخاصة بك \n\nOpenKeychain تحت الرخصة GPLv3 و لتحميل التطبيق من جوجل بلاي \n\n <small>(وأعد تشغيل التطبيق مرة أخرى)</small></string> - <string name="restart">اعادة تشغيل</string> - <string name="install">تثبيت</string> - <string name="offering">عرض ..</string> - <string name="waiting">انتظار ..</string> - <string name="no_pgp_key">OpenPGP-لايوجد مفتاح</string> - <string name="contact_has_no_pgp_key">Conversations::لا يستطيع تشفير الرساله\n\n<small>من فضلك أخبر صديقك بتنصيب تطبيق OpenPGP.</small></string> - <string name="no_pgp_keys">OpenPGP-لايوجد مفاتيح</string> - <string name="contacts_have_no_pgp_keys">Conversations::لا يستطيع تشفير الرساله\n\n<small>من فضلك أخبر صديقك بتنصيب تطبيق OpenPGP.</small></string> - <string name="encrypted_message_received"><i>تلقيت رساله مشفّرة .. لمسه بأناملك لعرضها.</i></string> - <string name="pref_general">عام</string> - <string name="pref_xmpp_resource">الريسورس</string> - <string name="pref_accept_files">ضبط استقبال الملفات</string> - <string name="pref_accept_files_size_summary">اقبل تلقائيا الملفات أقل من</string> - <string name="pref_notification_settings">اعدادات الإشعارات</string> - <string name="pref_notifications">الإشعارات</string> - <string name="pref_notifications_summary">أخبرني عندما تصل رساله جديده</string> - <string name="pref_vibrate">إعداد الإهتزاز</string> - <string name="pref_vibrate_summary">تفعيل الاهتزاز عندما تصل رساله جديده</string> - <string name="pref_sound">التبيه الصوتي</string> - <string name="pref_sound_summary">سماع صوت عندما تصل رساله</string> - <string name="pref_conference_notifications">الاشعارات من الغرف</string> - <string name="pref_advanced_options">اعدادات متقدمّة</string> - <string name="pref_never_send_crash">لا ترسل تقارير أخطاء</string> - <string name="pref_never_send_crash_summary">الغاء ارسال تقارير الأخطاء يقلل من فرص حل المشكلة سريعا فكن متعاون</string> - <string name="pref_confirm_messages">تأكيد الرسالة</string> - <string name="accept">قبول</string> - <string name="error">حدث خطأ ما</string> - <string name="pref_grant_presence_updates">منح تحديثات الظهور</string> - <string name="pref_grant_presence_updates_summary">اسأل واقبل السؤال عن تحديثات الظهور</string> - <string name="your_account">حسابك</string> - <string name="keys">مفاتيح</string> - <string name="send_presence_updates">ارسال تحديثات الظهور</string> - <string name="attach_choose_picture">اختيار صورة</string> - <string name="attach_take_picture">التقاط صورة</string> - <string name="error_file_not_found">الملف غير موجود</string> - <string name="account_status_unknown">غير معروف</string> - <string name="account_status_online">متصل</string> - <string name="account_status_offline">غير متصل</string> - <string name="account_status_unauthorized">غير مصرح له</string> - <string name="account_status_not_found">لا يمكن الاتصال بالسرفر</string> - <string name="account_status_no_internet">تحقق من اتصالك بالانترنت</string> - <string name="account_status_regis_fail">فشل تسجيل حساب بالسيرفر</string> - <string name="account_status_regis_conflict">اسم المستخدم مستخدم من قبل</string> - <string name="account_status_regis_success">تم تسجيل حسابك بنجاح</string> - <string name="account_status_regis_not_sup">تسجيل الحسابات غير متاح على هذا السرفر</string> - <string name="encryption_choice_none">رساله عادية</string> - <string name="encryption_choice_otr">OTRرسالة مشفرة عبر</string> - <string name="encryption_choice_pgp">OpenPGPرسالة مشفرة عبر</string> - <string name="mgmt_account_edit">تعديل الحساب</string> - <string name="mgmt_account_delete">حذف الحساب</string> - <string name="mgmt_account_publish_avatar">نشر الصورة الرمزية</string> - <string name="mgmt_account_enable">تفعيل الحساب</string> - <string name="mgmt_account_are_you_sure">هل أنت متأكد ؟</string> - <string name="mgmt_account_delete_confirm_text">اذا مسحت حسابك ستفقد جميع الرسائل المحفوظه !!</string> - <string name="attach_record_voice">تسجيل صوت</string> - <string name="account_settings_jabber_id">حساب جابر</string> - <string name="account_settings_password">كلمة السر</string> - <string name="account_settings_example_jabber_id">username@example.com</string> - <string name="account_settings_confirm_password">تأكيد كلمة السر</string> - <string name="password">كلمة السر</string> - <string name="confirm_password">تأكيد كلمة السر</string> - <string name="passwords_do_not_match">الكلمتان غير متطابقتان</string> - <string name="invalid_jid">حساب جابر غير صالح</string> - <string name="contact_status_online">متصل</string> - <string name="contact_status_free_to_chat">متاح للدردشة</string> - <string name="contact_status_away">بعيد</string> - <string name="contact_status_extended_away">بعيد جدا</string> - <string name="contact_status_do_not_disturb">مشغول</string> - <string name="contact_status_offline">غير متصل</string> - <string name="muc_details_conference">الغرف</string> - <string name="muc_details_other_members">المشتركين</string> - <string name="server_info_show_more">معلومات السرفر</string> - <string name="server_info_available">متاح</string> - <string name="server_info_unavailable">غير متاح</string> - <string name="last_seen_now">آخر ظهور الآن</string> - <string name="last_seen_min">آخر ظهور منذ 1 دقيقة</string> - <string name="last_seen_mins">آخر ظهور منذ %d دقيقة</string> - <string name="last_seen_hour">آخر ظهور منذ 1 ساعة</string> - <string name="last_seen_hours">آخر ظهور منذ %d ساعة</string> - <string name="last_seen_day">آخر ظهور منذ 1 يوم</string> - <string name="last_seen_days">آخر ظهور منذ %d يوم</string> - <string name="never_seen">لم يظهر متصلا حتى الآن</string> - <string name="verify">تأكيد</string> - <string name="conferences">الغرف</string> - <string name="search">بحث</string> - <string name="create_contact">اضافة جهة اتصال</string> - <string name="join_conference">دخول الغرف</string> - <string name="delete_contact">حذف جهة اتصال</string> - <string name="view_contact_details">عرض بيانات جهة الاتصال</string> - <string name="block_contact">حجب جهة اتصال</string> - <string name="unblock_contact">الغاء حجب جهة اتصال</string> - <string name="create">أضف</string> - <string name="contact_already_exists">جهة الاتصال موجودة لديك مسبقا</string> - <string name="join">دخول</string> - <string name="conference_address">اسم الغرفة كامل</string> - <string name="conference_address_example">room@conference.example.com</string> - <string name="save_as_bookmark">حفظ بالمفضلة</string> - <string name="delete_bookmark">حذف من المفضلة</string> - <string name="bookmark_already_exists">موجوده بالمفضلة سابقا</string> - <string name="you">انت</string> - <string name="action_edit_subject">تعديل موضوع الغرفة</string> - <string name="conference_not_found">الغرفة غير متاحه .. تأكد من عنوان الغرفة</string> - <string name="leave">غادر</string> - <string name="contact_added_you">جهة اتصال أضافتك </string> - <string name="publish">نشر</string> - <string name="publishing">نشر ...</string> - <string name="private_message_to">الى %s</string> - <string name="send_private_message_to">ارسال رسالة خاصة الى %s</string> - <string name="connect">اتصال</string> - <string name="account_already_exists">الحساب موجود من قبل</string> - <string name="next">التالي</string> - <string name="additional_information">معلومات اضافية</string> - <string name="skip">تجاهل</string> - <string name="disable_notifications">ايقاف التنبيهات</string> - <string name="disable_notifications_for_this_conversation">ايقاف التنبيهات لتلك المحادثة</string> - <string name="notifications_disabled">التنبيهات غير فعاله</string> - <string name="enable">تفعيل</string> - <string name="conference_requires_password">الغرفة تطلب كلمة مرور</string> - <string name="enter_password">أدخل كلمة المرور</string> - <string name="request_now">اطلب الآن</string> - <string name="ignore">تجاهل</string> - <string name="pref_expert_options_other">أخرى</string> - <string name="pref_conference_name">اسم الغرفة</string> - <string name="conference_banned">أنت مفصول في هذه الغرفة</string> - <string name="conference_members_only">الغرفة للأعضاء فقط</string> - <string name="conference_kicked">تم طردك من الغرفة</string> - <string name="not_connected_try_again">انقطع الإتصال .. حاول مرة أخرى</string> - <string name="message_options">خيارات الرساله</string> - <string name="copy_text">نسخ النص</string> - <string name="message_text">نص الرسالة</string> - <string name="account_details">تفاصيل الحساب</string> - <string name="scan">سكان</string> - <string name="confirm">تأكيد</string> - <string name="in_progress">جاري الاتصال</string> - <string name="failed">فشل الاتصال</string> - <string name="try_again">حاول مرة أخرى</string> - <string name="finish">انهاء</string> - <string name="verified">تأكيد!</string> - <string name="conversations_foreground_service">Conversations</string> - <string name="pref_keep_foreground_service">احتفظ بالتطبيق يعمل في المقدمة</string> - <string name="pref_keep_foreground_service_summary">منع نظام التشغيل من انهاء اتصالك</string> - <string name="choose_file">اختيار ملف</string> - <string name="receiving_x_file">اكتمل الإستلام %1$s (%2$d%% بنسبة)</string> - <string name="download_x_file">تنزيل %s</string> - <string name="file">ملف</string> - <string name="open_x_file">فتح %s</string> - <string name="sending_file">إكتمل الإرسال (%1$d%% بنسبة)</string> - <string name="preparing_file">إعداد ارسال الملفات</string> - <string name="x_file_offered_for_download">%s عرض وتنزيل</string> - <string name="cancel_transmission">الغاء الارسال</string> - <string name="file_transmission_failed">تعذر ارسال الملف</string> - <string name="file_deleted">تم مسح الملف</string> - <string name="no_application_found_to_open_file">لا يوجد تطبيق متاح لعرض الملف</string> - <string name="could_not_verify_fingerprint">لا يمكن التحقق من البصمة</string> - <string name="manually_verify">تأكيد يدوي</string> - <string name="are_you_sure_verify_fingerprint">هل ترغب في تأكيد بصمات OTR لجهات اتصالك ؟</string> - <string name="pref_show_dynamic_tags">عرض العلامات التلقائية</string> - <string name="pref_show_dynamic_tags_summary">عرض العلامات للقراءة فقط أسفل بيانات جهات الإتصال </string> - <string name="enable_notifications">تفعيل الإشعارات</string> - <string name="conference_with">انشاء غرفة بــ </string> - <string name="no_conference_server_found">سيرفر الغرف غير موجود</string> - <string name="conference_creation_failed">فشل انشاء الغرفة</string> - <string name="conference_created">تم انشاء الرغرفة بنجاح</string> - <string name="secret_accepted">الاجابة السريّة مقبولة</string> - <string name="reset">إعادة تعيين</string> - <string name="account_image_description">الصورة الرمزية للحساب</string> - <string name="copy_otr_clipboard_description">نسخ OTR بصمات الأصابع إلى الحافظة</string> - <string name="fetching_history_from_server">جلب المحفوظات من السرفر</string> - <string name="no_more_history_on_server">لا مزيد من المحفوظات بالسرفر</string> - <string name="updating">جاري التحديث..</string> - <string name="password_changed">تم تغيير كلمة السر!</string> - <string name="could_not_change_password">لايمكن تغيير كلمة السر</string> - <string name="otr_session_not_started">ارسل رساله لبدأ محادثة مشفّرة</string> - <string name="ask_question">أسال سؤال</string> - <string name="smp_explain_answer">سترغب جهة الإتصال بتأكيد بصمتك عبر السر المشترك بينكما لذلك أخبره تلميحا أو إسأله سؤالا يذكره بالسر ليكتبه برده</string> - <string name="shared_secret_hint_should_not_be_empty">التلميح الذي يراه صديقك لا يمكن ان يكون فارغ</string> - <string name="shared_secret_can_not_be_empty">السر المشترك بينكما لا يمكن ان يترك فارغا !!</string> - <string name="manual_verification_explanation">بعناية قارن بين بصمتك المعروضه أدناه وبصمة جهات اتصالك\n يمكنك استخدام أي شكل موثوق به للاتصال مثل بريد إلكتروني مشفر أو مكالمة هاتفية لتبادلها.</string> - <string name="change_password">تغيير كلمة المرور</string> - <string name="current_password">كلمة المرور الحالية</string> - <string name="new_password">كلمة مرور جديدة</string> - <string name="password_should_not_be_empty">كلمة المرور لا يمكن ان تبقى فارغة</string> - <string name="enable_all_accounts">تفعيل كل الحسابات</string> - <string name="disable_all_accounts">تعطيل كل الحسابات</string> - <string name="perform_action_with">تنفيذ الإجراء مع</string> - <string name="no_affiliation">زائر</string> - <string name="no_role">لا دور</string> - <string name="outcast">مفصول</string> - <string name="member">عضو</string> - <string name="advanced_mode">الوضع المتقدم</string> - <string name="grant_membership">منح عضوية</string> - <string name="remove_membership">إلغاء عضوية</string> - <string name="grant_admin_privileges">منح امتيازات الإداره</string> - <string name="remove_admin_privileges">إلغاء امتيازات الإدارة</string> - <string name="remove_from_room">إزالة من الغرفة</string> - <string name="could_not_change_affiliation">لا يمكن تغيير انتساب %s</string> - <string name="ban_from_conference">حظر من الغرفة</string> - <string name="removing_from_public_conference">تحاول انهاء تواجد %s الغرفه. سيتم فصله</string> - <string name="ban_now">حظر الآن</string> - <string name="could_not_change_role">لا يمكن تغيير دول %s</string> - <string name="public_conference">الوصول العام للغرفة</string> - <string name="private_conference">الخاص , اعضاء الغرفة فقط</string> - <string name="conference_options">اعدادت المؤتمر</string> - <string name="members_only">الخاص (الأعضاء فقط)</string> - <string name="non_anonymous">لا تسمح للمجهولين بالدخول</string> - <string name="modified_conference_options">تم تعديل اعدادات المؤتمر!</string> - <string name="could_not_modify_conference_options">لا يمكن تعديل اعدادات المؤتمر</string> - <string name="never">أبداً</string> - <string name="thirty_minutes">30 دقيقة</string> - <string name="one_hour">ساعة</string> - <string name="two_hours">ساعتين</string> - <string name="eight_hours">8 ساعات</string> - <string name="until_further_notice">حتى إشعار آخر</string> - <string name="pref_input_options">خيارات الادخال</string> - <string name="pref_enter_is_send">أدخل للإرسال</string> - <string name="pref_enter_is_send_summary">استخدام مفتاح الدخول لإرسال رسالة</string> - <string name="pref_display_enter_key">عرض مفتاح الادخال</string> - <string name="pref_display_enter_key_summary">تغيير مفتاح الرموز إلى مفتاح الدخول</string> - <string name="audio">صوت</string> - <string name="video">فيديو</string> - <string name="image">صورة</string> - <string name="pdf_document">مستند PDF</string> - <string name="apk">تطبيق اندرويد</string> - <string name="vcard">تواصل</string> - <string name="received_x_file">تم التلقي %s</string> - <string name="disable_foreground_service">ايقاف عرض تنويهات الخدمات على رئيسية الهاتف</string> - <string name="touch_to_open_conversations">ألمس لفتح المحادثات</string> - <string name="avatar_has_been_published">تم نشر الصورة!</string> - <string name="sending_x_file">ارسال %s</string> - <string name="offering_x_file">عرض %s</string> - <string name="hide_offline">اخفاء غير المتصلين</string> - <string name="disable_account">ايقاف الحساب</string> -</resources> diff --git a/src/main/res/values-ar/strings.xml b/src/main/res/values-ar/strings.xml index c757504a..2e59983c 100644 --- a/src/main/res/values-ar/strings.xml +++ b/src/main/res/values-ar/strings.xml @@ -1,2 +1,308 @@ <?xml version='1.0' encoding='UTF-8'?> -<resources/> +<resources> + <string name="action_settings">إعدادات</string> + <string name="action_add">محادثة جديدة</string> + <string name="action_accounts">إدارة الحسابات</string> + <string name="action_end_conversation">إنهاء المحادثة</string> + <string name="action_contact_details">بيانات جهة الإتصال</string> + <string name="action_muc_details">بيانات الغرفة</string> + <string name="action_secure">تشفير المحادثة</string> + <string name="action_add_account">إضافة حساب</string> + <string name="action_edit_contact">تعديل الإسم</string> + <string name="action_delete_contact">حذف من الإضافات</string> + <string name="action_block_contact">حجب جهة إتصال</string> + <string name="action_unblock_contact">إنهاء حجب جهة اتصال</string> + <string name="action_block_domain">حجب دومين</string> + <string name="action_unblock_domain">إنهاء حجب دومين</string> + <string name="title_activity_manage_accounts">إدارة الحسابات</string> + <string name="title_activity_settings">إعدادات</string> + <string name="title_activity_conference_details">بيانات الغرفه</string> + <string name="title_activity_contact_details">بيانات جهة الإتصال</string> + <string name="title_activity_sharewith">مشاركة مع محادثة</string> + <string name="title_activity_start_conversation">بدأ محادثة</string> + <string name="title_activity_choose_contact">إختيار جهة اتصال</string> + <string name="title_activity_block_list">قائمة المحجوبين</string> + <string name="just_now">الآن</string> + <string name="minute_ago">منذ 1 دقيقة</string> + <string name="minutes_ago">دقائق %d منذ</string> + <string name="unread_conversations">محادثات غير مقروءة</string> + <string name="sending">ارسال</string> + <string name="nick_in_use">اللقب مستخدم من قبل</string> + <string name="admin">مدير</string> + <string name="owner">مالك</string> + <string name="moderator">مشرف</string> + <string name="participant">مشترك</string> + <string name="visitor">زائر</string> + <string name="remove_contact_text">هل ترغب بحذف جهة الإتصال %s من اضافاتك? ستبقى محادثاتك معه محفوظه</string> + <string name="block_contact_text">هل ترغب في حجب %s من ارسال الرسائل لك?</string> + <string name="unblock_contact_text">هل ترغب في انهاء حجب %s والسماح له بمراسلتك?</string> + <string name="block_domain_text">هل تريد حجب جميع جهات الإتصال من %s?</string> + <string name="unblock_domain_text">الغاء حجب جميع جهات الإتصال من %s?</string> + <string name="contact_blocked">جهة الاتصال محجوبه</string> + <string name="remove_bookmark_text">هل ترغب في حذف %s من المفضلات? المحادثات المحفوظه ستبقى كما هي</string> + <string name="register_account">تسجيل حساب جديد في سيرفر</string> + <string name="change_password_on_server">تغيير كلمة المرور في سيرفر</string> + <string name="share_with">مشاركة مع</string> + <string name="start_conversation">بدأ محادثة</string> + <string name="invite_contact">دعوة صديق</string> + <string name="contacts">جهات الإتصال</string> + <string name="cancel">الغاء</string> + <string name="set">تعيين</string> + <string name="add">اضافة</string> + <string name="edit">تعديل</string> + <string name="delete">حذف</string> + <string name="block">حجب</string> + <string name="unblock">الغاء حجب</string> + <string name="save">حفظ</string> + <string name="ok">موافق</string> + <string name="crash_report_title">توقف التطبيق عن العمل</string> + <string name="crash_report_message">بموافقتك على ارسال تقارير الأخطاء ستساعد مبرمجي التطبيق على تحسين جودة التطبيق\n<b>تنويه:</b> سيرسل تقارير الأخطاء ان وجدت بأحد الحسابات التي أضفتها.</string> + <string name="send_now">ارسال الآن</string> + <string name="send_never">لا تسألني ثانية</string> + <string name="problem_connecting_to_account">لا يمكن تسجيل الدخول لحسابك</string> + <string name="problem_connecting_to_accounts">لا يمكن تسجيل الدخول بحساباتك</string> + <string name="touch_to_fix">المس الشاشه لعرض المحادثات</string> + <string name="attach_file">ارفاق ملف</string> + <string name="not_in_roster">جهة الاتصال ليست مضافه لديك هل ترغب في إضافتها ؟؟</string> + <string name="add_contact">اضافة جهة اتصال</string> + <string name="send_failed">فشل التسليم</string> + <string name="send_rejected">مرفوض</string> + <string name="preparing_image">اعداد صورة للإرسال</string> + <string name="action_clear_history">حذف سجل المحفوظات</string> + <string name="clear_conversation_history">حذف سجل المحفوظات للمحادثة</string> + <string name="clear_histor_msg">هل ترغب بحذف جميع الرسائل في تلك المحادثة?\n\n<b>تنويه:</b> هذا لن يؤثر على الرسائل المخزنة على الأجهزة أو أي أماكن أخرى.</string> + <string name="delete_messages">حذف الرسائل</string> + <string name="choose_presence">اختيار ظهورك لجهات الإتصال</string> + <string name="send_otr_message">OTRارساله رساله مشفره عبر</string> + <string name="send_pgp_message">OpenPGPارساله رساله مشفره عبر</string> + <string name="your_nick_has_been_changed">تم تغيير لقبك بنجاح</string> + <string name="send_unencrypted">إرسال بدون تشفير</string> + <string name="decryption_failed">فشل فك التشفير. ربما لم يكن لديك المفتاح الخاص الصحيح.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations :: يستخدم تطبيق آخر يسمى <b> OpenKeychain </b> لتشفير وفك تشفير الرسائل وإدارة المفاتيح العامة الخاصة بك \n\nOpenKeychain تحت الرخصة GPLv3 و لتحميل التطبيق من جوجل بلاي \n\n <small>(وأعد تشغيل التطبيق مرة أخرى)</small></string> + <string name="restart">اعادة تشغيل</string> + <string name="install">تثبيت</string> + <string name="offering">عرض ..</string> + <string name="waiting">انتظار ..</string> + <string name="no_pgp_key">OpenPGP-لايوجد مفتاح</string> + <string name="contact_has_no_pgp_key">Conversations::لا يستطيع تشفير الرساله\n\n<small>من فضلك أخبر صديقك بتنصيب تطبيق OpenPGP.</small></string> + <string name="no_pgp_keys">OpenPGP-لايوجد مفاتيح</string> + <string name="contacts_have_no_pgp_keys">Conversations::لا يستطيع تشفير الرساله\n\n<small>من فضلك أخبر صديقك بتنصيب تطبيق OpenPGP.</small></string> + <string name="pref_general">عام</string> + <string name="pref_xmpp_resource">الريسورس</string> + <string name="pref_accept_files">ضبط استقبال الملفات</string> + <string name="pref_accept_files_summary">اقبل تلقائيا الملفات أقل من</string> + <string name="pref_notifications">الإشعارات</string> + <string name="pref_notifications_summary">أخبرني عندما تصل رساله جديده</string> + <string name="pref_vibrate">إعداد الإهتزاز</string> + <string name="pref_vibrate_summary">تفعيل الاهتزاز عندما تصل رساله جديده</string> + <string name="pref_sound">التبيه الصوتي</string> + <string name="pref_sound_summary">سماع صوت عندما تصل رساله</string> + <string name="pref_never_send_crash">لا ترسل تقارير أخطاء</string> + <string name="pref_never_send_crash_summary">الغاء ارسال تقارير الأخطاء يقلل من فرص حل المشكلة سريعا فكن متعاون</string> + <string name="pref_confirm_messages">تأكيد الرسالة</string> + <string name="accept">قبول</string> + <string name="error">حدث خطأ ما</string> + <string name="pref_grant_presence_updates">منح تحديثات الظهور</string> + <string name="pref_grant_presence_updates_summary">اسأل واقبل السؤال عن تحديثات الظهور</string> + <string name="your_account">حسابك</string> + <string name="keys">مفاتيح</string> + <string name="send_presence_updates">ارسال تحديثات الظهور</string> + <string name="attach_choose_picture">اختيار صورة</string> + <string name="attach_take_picture">التقاط صورة</string> + <string name="error_file_not_found">الملف غير موجود</string> + <string name="account_status_unknown">غير معروف</string> + <string name="account_status_online">متصل</string> + <string name="account_status_offline">غير متصل</string> + <string name="account_status_unauthorized">غير مصرح له</string> + <string name="account_status_not_found">لا يمكن الاتصال بالسرفر</string> + <string name="account_status_no_internet">تحقق من اتصالك بالانترنت</string> + <string name="account_status_regis_fail">فشل تسجيل حساب بالسيرفر</string> + <string name="account_status_regis_conflict">اسم المستخدم مستخدم من قبل</string> + <string name="account_status_regis_success">تم تسجيل حسابك بنجاح</string> + <string name="account_status_regis_not_sup">تسجيل الحسابات غير متاح على هذا السرفر</string> + <string name="encryption_choice_otr">OTRرسالة مشفرة عبر</string> + <string name="encryption_choice_pgp">OpenPGPرسالة مشفرة عبر</string> + <string name="mgmt_account_edit">تعديل الحساب</string> + <string name="mgmt_account_delete">حذف الحساب</string> + <string name="mgmt_account_publish_avatar">نشر الصورة الرمزية</string> + <string name="mgmt_account_enable">تفعيل الحساب</string> + <string name="mgmt_account_are_you_sure">هل أنت متأكد ؟</string> + <string name="mgmt_account_delete_confirm_text">اذا مسحت حسابك ستفقد جميع الرسائل المحفوظه !!</string> + <string name="attach_record_voice">تسجيل صوت</string> + <string name="account_settings_jabber_id">حساب جابر</string> + <string name="account_settings_password">كلمة السر</string> + <string name="account_settings_example_jabber_id">username@example.com</string> + <string name="account_settings_confirm_password">تأكيد كلمة السر</string> + <string name="password">كلمة السر</string> + <string name="confirm_password">تأكيد كلمة السر</string> + <string name="passwords_do_not_match">الكلمتان غير متطابقتان</string> + <string name="invalid_jid">حساب جابر غير صالح</string> + <string name="contact_status_online">متصل</string> + <string name="contact_status_free_to_chat">متاح للدردشة</string> + <string name="contact_status_away">بعيد</string> + <string name="contact_status_extended_away">بعيد جدا</string> + <string name="contact_status_do_not_disturb">مشغول</string> + <string name="contact_status_offline">غير متصل</string> + <string name="muc_details_conference">الغرف</string> + <string name="muc_details_other_members">المشتركين</string> + <string name="server_info_show_more">معلومات السرفر</string> + <string name="server_info_available">متاح</string> + <string name="server_info_unavailable">غير متاح</string> + <string name="last_seen_now">آخر ظهور الآن</string> + <string name="last_seen_min">آخر ظهور منذ 1 دقيقة</string> + <string name="last_seen_mins">آخر ظهور منذ %d دقيقة</string> + <string name="last_seen_hour">آخر ظهور منذ 1 ساعة</string> + <string name="last_seen_hours">آخر ظهور منذ %d ساعة</string> + <string name="last_seen_day">آخر ظهور منذ 1 يوم</string> + <string name="last_seen_days">آخر ظهور منذ %d يوم</string> + <string name="never_seen">لم يظهر متصلا حتى الآن</string> + <string name="verify">تأكيد</string> + <string name="conferences">الغرف</string> + <string name="search">بحث</string> + <string name="create_contact">اضافة جهة اتصال</string> + <string name="join_conference">دخول الغرف</string> + <string name="delete_contact">حذف جهة اتصال</string> + <string name="view_contact_details">عرض بيانات جهة الاتصال</string> + <string name="block_contact">حجب جهة اتصال</string> + <string name="unblock_contact">الغاء حجب جهة اتصال</string> + <string name="create">أضف</string> + <string name="contact_already_exists">جهة الاتصال موجودة لديك مسبقا</string> + <string name="join">دخول</string> + <string name="conference_address">اسم الغرفة كامل</string> + <string name="conference_address_example">room@conference.example.com</string> + <string name="save_as_bookmark">حفظ بالمفضلة</string> + <string name="delete_bookmark">حذف من المفضلة</string> + <string name="bookmark_already_exists">موجوده بالمفضلة سابقا</string> + <string name="you">انت</string> + <string name="action_edit_subject">تعديل موضوع الغرفة</string> + <string name="conference_not_found">الغرفة غير متاحه .. تأكد من عنوان الغرفة</string> + <string name="leave">غادر</string> + <string name="contact_added_you">جهة اتصال أضافتك </string> + <string name="publish">نشر</string> + <string name="publishing">نشر ...</string> + <string name="private_message_to">الى %s</string> + <string name="send_private_message_to">ارسال رسالة خاصة الى %s</string> + <string name="connect">اتصال</string> + <string name="account_already_exists">الحساب موجود من قبل</string> + <string name="next">التالي</string> + <string name="additional_information">معلومات اضافية</string> + <string name="skip">تجاهل</string> + <string name="disable_notifications">ايقاف التنبيهات</string> + <string name="disable_notifications_for_this_conversation">ايقاف التنبيهات لتلك المحادثة</string> + <string name="enable">تفعيل</string> + <string name="conference_requires_password">الغرفة تطلب كلمة مرور</string> + <string name="enter_password">أدخل كلمة المرور</string> + <string name="request_now">اطلب الآن</string> + <string name="ignore">تجاهل</string> + <string name="pref_expert_options_other">أخرى</string> + <string name="pref_conference_name">اسم الغرفة</string> + <string name="conference_banned">أنت مفصول في هذه الغرفة</string> + <string name="conference_members_only">الغرفة للأعضاء فقط</string> + <string name="conference_kicked">تم طردك من الغرفة</string> + <string name="not_connected_try_again">انقطع الإتصال .. حاول مرة أخرى</string> + <string name="message_options">خيارات الرساله</string> + <string name="copy_text">نسخ النص</string> + <string name="message_text">نص الرسالة</string> + <string name="account_details">تفاصيل الحساب</string> + <string name="scan">سكان</string> + <string name="confirm">تأكيد</string> + <string name="in_progress">جاري الاتصال</string> + <string name="failed">فشل الاتصال</string> + <string name="try_again">حاول مرة أخرى</string> + <string name="finish">انهاء</string> + <string name="verified">تأكيد!</string> + <string name="conversations_foreground_service">Conversations</string> + <string name="pref_keep_foreground_service">احتفظ بالتطبيق يعمل في المقدمة</string> + <string name="pref_keep_foreground_service_summary">منع نظام التشغيل من انهاء اتصالك</string> + <string name="choose_file">اختيار ملف</string> + <string name="receiving_x_file">اكتمل الإستلام %1$s (%2$d%% بنسبة)</string> + <string name="download_x_file">تنزيل %s</string> + <string name="file">ملف</string> + <string name="open_x_file">فتح %s</string> + <string name="sending_file">إكتمل الإرسال (%1$d%% بنسبة)</string> + <string name="preparing_file">إعداد ارسال الملفات</string> + <string name="x_file_offered_for_download">%s عرض وتنزيل</string> + <string name="cancel_transmission">الغاء الارسال</string> + <string name="file_transmission_failed">تعذر ارسال الملف</string> + <string name="file_deleted">تم مسح الملف</string> + <string name="no_application_found_to_open_file">لا يوجد تطبيق متاح لعرض الملف</string> + <string name="could_not_verify_fingerprint">لا يمكن التحقق من البصمة</string> + <string name="manually_verify">تأكيد يدوي</string> + <string name="are_you_sure_verify_fingerprint">هل ترغب في تأكيد بصمات OTR لجهات اتصالك ؟</string> + <string name="pref_show_dynamic_tags">عرض العلامات التلقائية</string> + <string name="pref_show_dynamic_tags_summary">عرض العلامات للقراءة فقط أسفل بيانات جهات الإتصال </string> + <string name="enable_notifications">تفعيل الإشعارات</string> + <string name="conference_with">انشاء غرفة بــ </string> + <string name="no_conference_server_found">سيرفر الغرف غير موجود</string> + <string name="conference_creation_failed">فشل انشاء الغرفة</string> + <string name="conference_created">تم انشاء الرغرفة بنجاح</string> + <string name="secret_accepted">الاجابة السريّة مقبولة</string> + <string name="reset">إعادة تعيين</string> + <string name="account_image_description">الصورة الرمزية للحساب</string> + <string name="copy_otr_clipboard_description">نسخ OTR بصمات الأصابع إلى الحافظة</string> + <string name="fetching_history_from_server">جلب المحفوظات من السرفر</string> + <string name="no_more_history_on_server">لا مزيد من المحفوظات بالسرفر</string> + <string name="updating">جاري التحديث..</string> + <string name="password_changed">تم تغيير كلمة السر!</string> + <string name="could_not_change_password">لايمكن تغيير كلمة السر</string> + <string name="otr_session_not_started">ارسل رساله لبدأ محادثة مشفّرة</string> + <string name="ask_question">أسال سؤال</string> + <string name="smp_explain_answer">سترغب جهة الإتصال بتأكيد بصمتك عبر السر المشترك بينكما لذلك أخبره تلميحا أو إسأله سؤالا يذكره بالسر ليكتبه برده</string> + <string name="shared_secret_hint_should_not_be_empty">التلميح الذي يراه صديقك لا يمكن ان يكون فارغ</string> + <string name="shared_secret_can_not_be_empty">السر المشترك بينكما لا يمكن ان يترك فارغا !!</string> + <string name="manual_verification_explanation">بعناية قارن بين بصمتك المعروضه أدناه وبصمة جهات اتصالك\n يمكنك استخدام أي شكل موثوق به للاتصال مثل بريد إلكتروني مشفر أو مكالمة هاتفية لتبادلها.</string> + <string name="change_password">تغيير كلمة المرور</string> + <string name="current_password">كلمة المرور الحالية</string> + <string name="new_password">كلمة مرور جديدة</string> + <string name="password_should_not_be_empty">كلمة المرور لا يمكن ان تبقى فارغة</string> + <string name="enable_all_accounts">تفعيل كل الحسابات</string> + <string name="disable_all_accounts">تعطيل كل الحسابات</string> + <string name="perform_action_with">تنفيذ الإجراء مع</string> + <string name="no_affiliation">زائر</string> + <string name="no_role">لا دور</string> + <string name="outcast">مفصول</string> + <string name="member">عضو</string> + <string name="advanced_mode">الوضع المتقدم</string> + <string name="grant_membership">منح عضوية</string> + <string name="remove_membership">إلغاء عضوية</string> + <string name="grant_admin_privileges">منح امتيازات الإداره</string> + <string name="remove_admin_privileges">إلغاء امتيازات الإدارة</string> + <string name="remove_from_room">إزالة من الغرفة</string> + <string name="could_not_change_affiliation">لا يمكن تغيير انتساب %s</string> + <string name="ban_from_conference">حظر من الغرفة</string> + <string name="removing_from_public_conference">تحاول انهاء تواجد %s الغرفه. سيتم فصله</string> + <string name="ban_now">حظر الآن</string> + <string name="could_not_change_role">لا يمكن تغيير دول %s</string> + <string name="public_conference">الوصول العام للغرفة</string> + <string name="private_conference">الخاص , اعضاء الغرفة فقط</string> + <string name="conference_options">اعدادت المؤتمر</string> + <string name="non_anonymous">لا تسمح للمجهولين بالدخول</string> + <string name="modified_conference_options">تم تعديل اعدادات المؤتمر!</string> + <string name="could_not_modify_conference_options">لا يمكن تعديل اعدادات المؤتمر</string> + <string name="never">أبداً</string> + <string name="thirty_minutes">30 دقيقة</string> + <string name="one_hour">ساعة</string> + <string name="two_hours">ساعتين</string> + <string name="eight_hours">8 ساعات</string> + <string name="until_further_notice">حتى إشعار آخر</string> + <string name="pref_enter_is_send">أدخل للإرسال</string> + <string name="pref_enter_is_send_summary">استخدام مفتاح الدخول لإرسال رسالة</string> + <string name="pref_display_enter_key">عرض مفتاح الادخال</string> + <string name="pref_display_enter_key_summary">تغيير مفتاح الرموز إلى مفتاح الدخول</string> + <string name="audio">صوت</string> + <string name="video">فيديو</string> + <string name="image">صورة</string> + <string name="pdf_document">مستند PDF</string> + <string name="apk">تطبيق اندرويد</string> + <string name="vcard">تواصل</string> + <string name="received_x_file">تم التلقي %s</string> + <string name="disable_foreground_service">ايقاف عرض تنويهات الخدمات على رئيسية الهاتف</string> + <string name="touch_to_open_conversations">ألمس لفتح المحادثات</string> + <string name="avatar_has_been_published">تم نشر الصورة!</string> + <string name="sending_x_file">ارسال %s</string> + <string name="offering_x_file">عرض %s</string> + <string name="hide_offline">اخفاء غير المتصلين</string> + <string name="disable_account">ايقاف الحساب</string> + <string name="dialog_manage_certs_negativebutton">الغاء</string> +</resources> diff --git a/src/main/res/values-bg/strings.xml b/src/main/res/values-bg/strings.xml index 73e64331..62e390ab 100644 --- a/src/main/res/values-bg/strings.xml +++ b/src/main/res/values-bg/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Защитен разговор</string> <string name="action_add_account">Добавяне на профил</string> <string name="action_edit_contact">Редактиране на името</string> - <string name="action_add_phone_book">Добавяне към списъка с телефонни номера</string> + <string name="action_add_phone_book">Добавяне към адресния указател</string> <string name="action_delete_contact">Изтриване от списъка</string> <string name="action_block_contact">Блокиране на контакта</string> <string name="action_unblock_contact">Деблокиране на контакта</string> @@ -27,8 +27,9 @@ <string name="minute_ago">преди 1 минута</string> <string name="minutes_ago">преди %d минути</string> <string name="unread_conversations">непрочетени разговори</string> - <string name="sending">изпращане...</string> - <string name="encrypted_message">Дешифроване на съобщението. Моля, изчакайте...</string> + <string name="sending">изпращане…</string> + <string name="message_decrypting">Дешифроване на съобщението. Моля, изчакайте…</string> + <string name="pgp_message">Съобщение, шифр. чрез OpenPGP</string> <string name="nick_in_use">Псевдонимът вече се използва</string> <string name="admin">Администратор</string> <string name="owner">Собственик</string> @@ -44,7 +45,7 @@ <string name="remove_bookmark_text">Искате ли да премахнете отметката за %s? Разговорът, свързан с тази отметка, няма да бъде премахнат.</string> <string name="register_account">Регистриране на нов профил на сървъра</string> <string name="change_password_on_server">Промяна на паролата в сървъра</string> - <string name="share_with">Споделяне с...</string> + <string name="share_with">Споделяне с…</string> <string name="start_conversation">Започване на разговор</string> <string name="invite_contact">Канене на контакт</string> <string name="contacts">Контакти</string> @@ -74,11 +75,13 @@ <string name="clear_conversation_history">Изчистване на историята на разговорите</string> <string name="clear_histor_msg">Искате ли да изтриете всички съобщения от този разговор?\n\n<b>Внимание:</b> Това няма да изтрие съобщенията, съхранявани на други устройства или на сървърите.</string> <string name="delete_messages">Изтриване на съобщенията</string> - <string name="also_end_conversation">Този разговор да приключи след това</string> + <string name="also_end_conversation">Приключване на този разговор след това</string> <string name="choose_presence">Изберете присъствие за контакта</string> - <string name="send_plain_text_message">Изпращане на обикновено текстово съобщение</string> - <string name="send_otr_message">Изпращане на съобщение, шифровано чрез OTP</string> - <string name="send_pgp_message">Изпращане на съобщение, шифровано чрез OpenPGP</string> + <string name="send_unencrypted_message">Изпр. на нешифр. съобщение</string> + <string name="send_otr_message">Изпр. на съобщение, шифр. чрез OTP</string> + <string name="send_omemo_message">Изпр. на съобщение, шифр. чрез OMEMO</string> + <string name="send_omemo_x509_message">Изпр. на съобщение, шифр. чрез v\\OMEMO</string> + <string name="send_pgp_message">Изпр. на съобщение, шифр. чрез OpenPGP</string> <string name="your_nick_has_been_changed">Псевдонимът Ви беше променен</string> <string name="send_unencrypted">Изпращане нешифровано</string> <string name="decryption_failed">Неуспешно дешифроване. Възможно е да нямате правилния частен ключ.</string> @@ -86,35 +89,34 @@ <string name="openkeychain_required_long">Conversations използва външно приложение с име <b>OpenKeychain</b>, за да шифрова и дешифрова съобщенията и да управлява публичните Ви ключове.\n\nOpenKeychain е лицензирано под условията на GPLv3 и е налично в F-Droid и Google Play.\n\n<small>(Моля, рестартирайте Conversations след това.)</small></string> <string name="restart">Рестартиране</string> <string name="install">Инсталиране</string> - <string name="offering">предлагане...</string> - <string name="waiting">изчакване...</string> + <string name="openkeychain_not_installed">Моля, инсталирайте OpenKeychain</string> + <string name="offering">предлагане…</string> + <string name="waiting">изчакване…</string> <string name="no_pgp_key">Не е открит OpenPGP ключ</string> <string name="contact_has_no_pgp_key">Conversations не може да шифрова съобщенията Ви, тъй като Вашият контакт не обявява публичния си ключ.\n\n<small>Моля, помолете го/я да инсталира и настрои OpenPGP.</small></string> <string name="no_pgp_keys">Не са открити OpenPGP ключове</string> <string name="contacts_have_no_pgp_keys">Conversations не може да шифрова съобщенията Ви, тъй като Вашите контакти не обявяват публичните си ключове.\n\n<small>Моля, помолете го да инсталират и настроят OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Получено е шифровано съобщение. Докоснете, за да го прегледате и дешифровате.</i></string> + <string name="encrypted_message_received"><i>Получено е шифровано съобщение. Докоснете, за да го дешифровате.</i></string> <string name="pref_general">Общи</string> <string name="pref_xmpp_resource">XMPP ресурс</string> <string name="pref_xmpp_resource_summary">Името, с което се определя този клиент</string> <string name="pref_accept_files">Приемане на файлове</string> - <string name="pref_accept_files_size_summary">Автоматично приемане на файлове с размер, по-малък от...</string> - <string name="pref_notification_settings">Настройки за известията</string> + <string name="pref_accept_files_summary">Автоматично приемане на файлове с размер, по-малък от…</string> + <string name="pref_notification_settings">Известие</string> <string name="pref_notifications">Известия</string> <string name="pref_notifications_summary">Известяване при получаване на ново съобщение</string> <string name="pref_vibrate">Вибрация</string> <string name="pref_vibrate_summary">Също така да има и вибрация при получаване на ново съобщение</string> <string name="pref_sound">Звук</string> <string name="pref_sound_summary">Изпълнение на звук с известието</string> - <string name="pref_conference_notifications">Известия за беседите</string> - <string name="pref_conference_notifications_summary">Известяване винаги, когато пристигне ново съобщение в беседа, а не само когато тя е отбелязана</string> <string name="pref_notification_grace_period">Продължителност на отсрочване на известията</string> <string name="pref_notification_grace_period_summary">Изключва известията за кратко, след като бъде получено копие на съобщение</string> - <string name="pref_advanced_options">Разширени настройки</string> + <string name="pref_advanced_options">Разширени</string> <string name="pref_never_send_crash">Никога да не се изпращат доклади за сривове</string> <string name="pref_never_send_crash_summary">Изпращайки проследявания на стека, Вие помагате за непрекъснатото развитие на Conversations</string> <string name="pref_confirm_messages">Потвърждаване на съобщенията</string> <string name="pref_confirm_messages_summary">Уведомява контакта Ви, че сте приели и прочели съобщението му</string> - <string name="pref_ui_options">Настройки на интерфейса</string> + <string name="pref_ui_options">Потр. интерфейс</string> <string name="openpgp_error">OpenKeychain докладва за грешка</string> <string name="error_decrypting_file">В/И грешка при дешифроването на файла</string> <string name="accept">Приемане</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">Сървърът не поддържа регистриране</string> <string name="account_status_security_error">Грешка в сигурността</string> <string name="account_status_incompatible_server">Несъвместим сървър</string> - <string name="encryption_choice_none">Обикновен текст</string> + <string name="encryption_choice_unencrypted">Нешифровано</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Редактиране на профила</string> <string name="mgmt_account_delete">Изтриване на профила</string> <string name="mgmt_account_disable">Временно деактивиране</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">Паролите са различни</string> <string name="invalid_jid">Това не е правилен Jabber идентификатор</string> <string name="error_out_of_memory">Няма достатъчно памет. Изображението е твърде голямо.</string> - <string name="add_phone_book_text">Искате ли да добавите %s в списъка си от телефонни контакти?</string> + <string name="add_phone_book_text">Искате ли да добавите %s към адресния си указател?</string> <string name="contact_status_online">на линия</string> <string name="contact_status_free_to_chat">свободен за разговор</string> <string name="contact_status_away">отсъстващ</string> @@ -186,7 +189,9 @@ <string name="server_info_blocking">XEP-0191: Команда за блокиране</string> <string name="server_info_roster_version">XEP-0237: Поддържане на версия на списъка</string> <string name="server_info_stream_management">XEP-0198: Управление на потоците</string> - <string name="server_info_pep">XEP-0163: PEP (Аватари)</string> + <string name="server_info_pep">XEP-0163: PEP (Аватари / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: Качване на файл през HTTP</string> + <string name="server_info_push">XEP-0357: Изпращане</string> <string name="server_info_available">налично</string> <string name="server_info_unavailable">не е налично</string> <string name="missing_public_keys">Липсват обявления за публичен ключ</string> @@ -199,22 +204,33 @@ <string name="last_seen_days">последно видян преди %d дни</string> <string name="never_seen">не е виждан никога</string> <string name="install_openkeychain">Шифровано съобщение. Моля, инсталирайте OpenKeychain, за да го дешифровате.</string> - <string name="unknown_otr_fingerprint">Непознат OTR отпечатък</string> + <string name="unknown_otr_fingerprint">Непознат отпечатък OTR</string> <string name="openpgp_messages_found">Открити са съобщения, шифровани чрез OpenPGP</string> <string name="reception_failed">Неуспешно получаване</string> <string name="your_fingerprint">Вашият отпечатък</string> - <string name="otr_fingerprint">OTR отпечатък</string> + <string name="otr_fingerprint">Отпечатък OTR</string> + <string name="omemo_fingerprint">Отпечатък OMEMO</string> + <string name="omemo_fingerprint_x509">Отпечатък v\\OMEMO</string> + <string name="omemo_fingerprint_selected_message">Отпечатък OMEMO на съобщението</string> + <string name="omemo_fingerprint_x509_selected_message">Отпечатък v\\OMEMO на съобщението</string> + <string name="this_device_omemo_fingerprint">Собствен отпечатък OMEMO</string> + <string name="other_devices">Други устройства</string> + <string name="trust_omemo_fingerprints">Доверяване на отпечатъци OMEMO</string> + <string name="fetching_keys">Изтегляне на ключове…</string> + <string name="done">Готово</string> <string name="verify">Потвърждаване</string> <string name="decrypt">Дешифроване</string> <string name="conferences">Беседи</string> <string name="search">Търсене</string> <string name="create_contact">Създаване на контакт</string> + <string name="enter_contact">Въведете контакт</string> <string name="join_conference">Присъединяване към беседа</string> <string name="delete_contact">Изтриване на контакт</string> <string name="view_contact_details">Преглед на подр. за контакта</string> <string name="block_contact">Блокиране на контакт</string> <string name="unblock_contact">Деблокиране на контакт</string> <string name="create">Създаване</string> + <string name="select">Избиране</string> <string name="contact_already_exists">Контактът вече съществува</string> <string name="join">Присъединяване</string> <string name="conference_address">Адрес на беседата</string> @@ -225,6 +241,7 @@ <string name="you">Вие</string> <string name="action_edit_subject">Редактиране на темата на беседата</string> <string name="conference_not_found">Беседата не е открита</string> + <string name="conference_unknown_error">Беше получена непозната грешка</string> <string name="leave">Напускане</string> <string name="contact_added_you">Контактът е добавен във Вашия списък от контакти</string> <string name="add_back">Добавяне обратно</string> @@ -232,7 +249,7 @@ <string name="publish">Публикуване</string> <string name="touch_to_choose_picture">Докоснете аватара, за да изберете изображение от галерията</string> <string name="publish_avatar_explanation">Забележка: Всеки, абониран за актуализации на присъствието Ви, ще може да вижда тази снимка.</string> - <string name="publishing">Публикуване...</string> + <string name="publishing">Публикуване…</string> <string name="error_publish_avatar_server_reject">Сървърът отказа Вашето публикуване</string> <string name="error_publish_avatar_converting">Нещо се обърка при преобразуването на снимката Ви</string> <string name="error_saving_avatar">Неуспешно запазване на аватара на диска</string> @@ -249,7 +266,6 @@ <string name="skip">Пропускане</string> <string name="disable_notifications">Изключване на известията</string> <string name="disable_notifications_for_this_conversation">Изключване на известията за този разговор</string> - <string name="notifications_disabled">Известията са изключени</string> <string name="enable">Включване</string> <string name="conference_requires_password">Беседата изисква парола</string> <string name="enter_password">Въведете парола</string> @@ -260,12 +276,14 @@ <string name="sure_delete_fingerprint">Сигурни ли сте, че искате да изтриете този отпечатък?</string> <string name="ignore">Пренебрегване</string> <string name="without_mutual_presence_updates"><b>Внимание:</b> Изпращането на това без съвместни актуализации на присъствието може да доведе до неочаквани проблеми.\n\n<small>Погледнете подробностите за контакта, за да проверите дали сте абониран за актуализации на присъствието.</small></string> - <string name="pref_encryption_settings">Настройки за шифроване</string> + <string name="pref_security_settings">Сигурност</string> <string name="pref_force_encryption">Налагане на шифроване в двете посоки</string> <string name="pref_force_encryption_summary">Съобщенията да се изпращат винаги шифровани (освен в беседите)</string> + <string name="pref_allow_message_correction">Позволяване на поправянето на съобщения</string> + <string name="pref_allow_message_correction_summary">Позволяване на контактите да редактират съобщенията си след като са ги изпратили.</string> <string name="pref_dont_save_encrypted">Шифрованите съобщения да не се запазват</string> <string name="pref_dont_save_encrypted_summary">Внимание: Това може да доведе до загуба на съобщения</string> - <string name="pref_expert_options">Настройки за напреднали</string> + <string name="pref_expert_options">Експертни настройки</string> <string name="pref_expert_options_summary">Моля, бъдете внимателни с тези</string> <string name="title_activity_about">Относно Conversations</string> <string name="pref_about_conversations_summary">Информация за версията и лицензите</string> @@ -283,7 +301,10 @@ <string name="pref_expert_options_other">Други</string> <string name="pref_conference_name">Име на беседата</string> <string name="pref_conference_name_summary">Използване на темата на стаята вместо JID идентификатора за беседите</string> - <string name="toast_message_otr_fingerprint">OTR отпечатъкът е копиран!</string> + <string name="pref_autojoin">Автоматично присъединяване към беседите</string> + <string name="pref_autojoin_summary">Спазване на флага за автоматично присъединяване в отметките на беседите</string> + <string name="toast_message_otr_fingerprint">Отпечатъкът OTR е копиран!</string> + <string name="toast_message_omemo_fingerprint">Отпечатъкът OMEMO е копиран!</string> <string name="conference_banned">Достъпът Ви до тази беседа беше забранен</string> <string name="conference_members_only">Тази беседа е само за членове</string> <string name="conference_kicked">Бяхте изритан от тази конференция</string> @@ -307,7 +328,6 @@ <string name="verify_otr">Проверка на OTR</string> <string name="remote_fingerprint">Отдалечен отпечатък</string> <string name="scan">сканиране</string> - <string name="or_touch_phones">(или докоснете телефоните)</string> <string name="smp">Протокол „Socialist Millionaire“</string> <string name="shared_secret_hint">Подсказка или въпрос</string> <string name="shared_secret_secret">Обща тайна</string> @@ -324,9 +344,12 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Услугата да е на преден план</string> <string name="pref_keep_foreground_service_summary">Предотвратява прекъсването на връзката Ви от операционната система</string> + <string name="pref_export_logs">Изнасяне на журналите</string> + <string name="pref_export_logs_summary">Записване на журналите в картата с памет</string> + <string name="notification_export_logs_title">Записване на журналите в картата с памет</string> <string name="choose_file">Изберете файл</string> <string name="receiving_x_file">Получаване на %1$s (%2$d%% завършено)</string> - <string name="download_x_file">Изтегляне на %s</string> + <string name="download_x_file">Сваляне на %s</string> <string name="file">файл</string> <string name="open_x_file">Отваряне на %s</string> <string name="sending_file">изпращане (%1$d%% завършено)</string> @@ -338,21 +361,32 @@ <string name="no_application_found_to_open_file">Няма намерено приложение за отваряне на файла</string> <string name="could_not_verify_fingerprint">Неуспешна проверка на отпечатъка</string> <string name="manually_verify">Ръчна проверка</string> - <string name="are_you_sure_verify_fingerprint">Сигурни ли сте, че искате да проверите OTR отпечатъка на контактите си?</string> + <string name="are_you_sure_verify_fingerprint">Сигурни ли сте, че искате да проверите отпечатъка OTR на контактите си?</string> <string name="pref_show_dynamic_tags">Динамични етикети</string> <string name="pref_show_dynamic_tags_summary">Показване на етикети, предназначени само за четене под контактите</string> <string name="enable_notifications">Включване на известията</string> - <string name="conference_with">Започване на беседа с...</string> + <string name="conference_with">Започване на беседа с…</string> <string name="no_conference_server_found">Не е открит сървър за беседата</string> <string name="conference_creation_failed">Неуспешно създаване на беседа!</string> <string name="conference_created">Беседата беше създадена!</string> <string name="secret_accepted">Тайната е приета!</string> <string name="reset">Възстановяване</string> <string name="account_image_description">Аватар на профила</string> - <string name="copy_otr_clipboard_description">Копиране на OTR отпечатъка</string> + <string name="copy_otr_clipboard_description">Копиране на отпечатъка OTR</string> + <string name="copy_omemo_clipboard_description">Копиране на отпечатъка OMEMO</string> + <string name="regenerate_omemo_key">Повторно създаване на ключа OMEMO</string> + <string name="wipe_omemo_pep">Изчистване на другите устройства от PEP</string> + <string name="clear_other_devices">Премахване на устройствата</string> + <string name="clear_other_devices_desc">Сигурни ли сте, че искате да премахнете всички останали устройства от обявлението OMEMO? Следващия път, когато устройствата Ви се свържат, те ще обявят себе си отново, но може да не получат съобщенията, изпратени междувременно.</string> + <string name="purge_key">Изтриване на ключа</string> + <string name="purge_key_desc_part1">Сигурни ли сте, че искате да изтриете този ключ?</string> + <string name="purge_key_desc_part2">След това той ще бъде необратимо приет за грешен и повече няма да можете да създавате сесии с него.</string> + <string name="error_no_keys_to_trust_server_error">Няма ключове, които могат да бъдат използвани за този контакт.\nИзтеглянето на нови ключове от сървъра беше неуспешно. Възможно е да има проблем със сървъра на контактите Ви.</string> + <string name="error_no_keys_to_trust">Няма ключове, които могат да бъдат използвани за този контакт. Ако сте изчистили някои от ключовете му, то той трябва да създаде нови.</string> + <string name="error_trustkeys_title">Грешка</string> <string name="fetching_history_from_server">Получаване на историята от сървъра</string> <string name="no_more_history_on_server">Няма повече история на сървъра</string> - <string name="updating">Актуализиране...</string> + <string name="updating">Актуализиране…</string> <string name="password_changed">Паролата е променена!</string> <string name="could_not_change_password">Неуспешна промяна на паролата</string> <string name="otr_session_not_started">Изпратете съобщение, за да започнете нешифрован разговор</string> @@ -387,8 +421,10 @@ <string name="public_conference">Публично достъпни беседи</string> <string name="private_conference">Частни беседи, само за членове</string> <string name="conference_options">Настройки на беседата</string> - <string name="members_only">Частна (само за членове)</string> + <string name="members_only">Частно, само за членове</string> <string name="non_anonymous">Не-анонимна</string> + <string name="moderated">С модератор</string> + <string name="you_are_not_participating">Вие не участвате</string> <string name="modified_conference_options">Настройките на беседата бяха променени!</string> <string name="could_not_modify_conference_options">Неуспешна промяна на настройките на беседата</string> <string name="never">Никога</string> @@ -397,7 +433,7 @@ <string name="two_hours">2 часа</string> <string name="eight_hours">8 часа</string> <string name="until_further_notice">До отмяна</string> - <string name="pref_input_options">Настройки за въвеждане</string> + <string name="pref_input_options">Въвеждане</string> <string name="pref_enter_is_send">Enter изпраща</string> <string name="pref_enter_is_send_summary">Натискането на клавиша Enter изпраща съобщението</string> <string name="pref_display_enter_key">Показване на клавиша Enter</string> @@ -416,7 +452,7 @@ <string name="offering_x_file">Предлагане на %s</string> <string name="hide_offline">Скриване на тези извън линия</string> <string name="disable_account">Деактивиране на профила</string> - <string name="contact_is_typing">%s пише...</string> + <string name="contact_is_typing">%s пише…</string> <string name="contact_has_stopped_typing">%s спря да пише</string> <string name="pref_chat_states">Известия за писането</string> <string name="pref_chat_states_summary">Позволяване на контакта Ви да вижда, когато пишете ново съобщение</string> @@ -427,7 +463,6 @@ <string name="received_location">Получено местоположение</string> <string name="title_undo_swipe_out_conversation">Conversation се затвори</string> <string name="title_undo_swipe_out_muc">Напуснахте беседата</string> - <string name="pref_certificate_options">Настройки на сертификата</string> <string name="pref_dont_trust_system_cas_title">Да не се вярва на системните сертификати</string> <string name="pref_dont_trust_system_cas_summary">Всички сертификати трябва да бъдат одобрени на ръка</string> <string name="pref_remove_trusted_certificates_title">Премахване на сертификатите</string> @@ -449,6 +484,75 @@ <string name="none">Нищо</string> <string name="recently_used">Използвани наскоро</string> <string name="choose_quick_action">Изберете бързо действие</string> - <string name="file_not_found_on_remote_host">Файлът не е открит на отдалечения сървър</string> <string name="search_for_contacts_or_groups">Търсене на контакти или групи</string> + <string name="send_private_message">Изпращане на лично съобщение</string> + <string name="user_has_left_conference">%s напусна беседата!</string> + <string name="username">Потребителско име</string> + <string name="username_hint">Потребителско име</string> + <string name="invalid_username">Това не е правилно потребителско име</string> + <string name="download_failed_server_not_found">Неуспешно сваляне: Сървърът не е открит</string> + <string name="download_failed_file_not_found">Неуспешно сваляне: Файлът не е открит</string> + <string name="download_failed_could_not_connect">Неуспешно сваляне: Неуспешна връзка със сървъра</string> + <string name="account_status_tor_unavailable">Мрежата на Тор е недостъпна</string> + <string name="server_info_broken">Повредено</string> + <string name="pref_presence_settings">Присъствие</string> + <string name="pref_away_when_screen_off">Отсъстващ, когато екранът е изключен</string> + <string name="pref_away_when_screen_off_summary">Преминава в състояние „отсъстващ“ когато екранът бъде изключен</string> + <string name="pref_xa_on_silent_mode">Недостъпен, в тих режим</string> + <string name="pref_xa_on_silent_mode_summary">Преминава в състояние „недостъпен“ когато устройството е в тих режим</string> + <string name="pref_show_connection_options">Разширени настройки за връзката</string> + <string name="pref_show_connection_options_summary">Показване на настройките за сървър и порт при установка на профил</string> + <string name="hostname_example">xmpp.example.com</string> + <string name="action_add_account_with_certificate">Добавяне на профил със сертификат</string> + <string name="unable_to_parse_certificate">Неуспешно прочитане на сертификата</string> + <string name="authenticate_with_certificate">Оставете празно за удостоверяване със сертификат</string> + <string name="mam_prefs">Настройки за архивирането</string> + <string name="server_side_mam_prefs">Настройки за архивирането на сървъра</string> + <string name="fetching_mam_prefs">Получаване на настройките за архивирането. Моля, изчакайте…</string> + <string name="unable_to_fetch_mam_prefs">Неуспешно получаване на настройките за архивирането</string> + <string name="captcha_ocr">Текст за проверка</string> + <string name="captcha_required">Проверката е задължителна</string> + <string name="captcha_hint">въведете текста от изображението</string> + <string name="certificate_chain_is_not_trusted">Сертификатът не е потвърден</string> + <string name="jid_does_not_match_certificate">Jabber идентификатора не съответства на сертификата</string> + <string name="action_renew_certificate">Подновяване на сертификата</string> + <string name="error_fetching_omemo_key">Грешка при получаването на ключа за OMEMO!</string> + <string name="verified_omemo_key_with_certificate">Ключът за OMEMO беше потвърден със сертификат!</string> + <string name="device_does_not_support_certificates">Устройството Ви не поддържа избраните клиентски сертификати!</string> + <string name="pref_connection_options">Връзка</string> + <string name="account_settings_hostname">Име на сървър</string> + <string name="account_settings_port">Порт</string> + <string name="not_a_valid_port">Това не е правилен номер на порт</string> + <string name="not_valid_hostname">Това не е правилно име на сървър</string> + <string name="connected_accounts">%1$d от %2$d свързани профила</string> + <plurals name="x_messages"> + <item quantity="one">%d съобщение</item> + <item quantity="other">%d съобщения</item> + </plurals> + <string name="shared_file_with_x">Файлът е споделен с %s</string> + <string name="shared_image_with_x">Изображението е споделено с %s</string> + <string name="no_storage_permission">Conversations се нуждае от достъп до външно място за съхранение</string> + <string name="sync_with_contacts">Синхронизиране с контактите</string> + <string name="sync_with_contacts_long">Conversations иска да съчетае Вашия списък в XMPP с контактите Ви, за да показва пълните им имена и снимки..\n\nConversations единствено ще чете контактите Ви и ще ги използва вътрешно, без да ги качва на сървъра Ви.\n\nЩе бъдете помолен/а за позволение за достъп до контактите Ви.</string> + <string name="certificate_information">Информация за сертификата</string> + <string name="certificate_subject">Обект</string> + <string name="certificate_issuer">Издател</string> + <string name="certificate_cn">Име</string> + <string name="certificate_o">Организация</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(Няма)</string> + <string name="certificate_not_found">Няма намерен сертификат</string> + <string name="notify_on_all_messages">Известяване за всички съобщения</string> + <string name="notify_only_when_highlighted">Известяване само на осветените</string> + <string name="notify_never">Известията са изключени</string> + <string name="notify_paused">Известията са спрени временно</string> + <string name="always">Винаги</string> + <string name="automatically">Автоматично</string> + <string name="battery_optimizations_enabled">Оптимизациите за използв. на батерията са вкл.</string> + <string name="battery_optimizations_enabled_explained">Устройството Ви прилага сериозни оптимизации за използването на батерията върху Conversations, а те може да доведат до забавени известия и дори пропуснати съобщения.\nПрепоръчително е до ги изключите.</string> + <string name="battery_optimizations_enabled_dialog">Устройството Ви прилага сериозни оптимизации за използването на батерията върху Conversations, а те може да доведат до забавени известия и дори пропуснати съобщения.\n\nСега ще Ви бъде предложено да ги изключите.</string> + <string name="disable">Изключване</string> + <string name="selection_too_large">Избраната област е твърде голяма</string> + <string name="no_accounts">(Няма активирани профили)</string> + <string name="this_field_is_required">Това поле е задължително</string> </resources> diff --git a/src/main/res/values-ca/strings.xml b/src/main/res/values-ca/strings.xml index 6b673071..6f48ab63 100644 --- a/src/main/res/values-ca/strings.xml +++ b/src/main/res/values-ca/strings.xml @@ -9,7 +9,6 @@ <string name="action_secure">Conversa segura</string> <string name="action_add_account">Afegir compte</string> <string name="action_edit_contact">Edita el nom</string> - <string name="action_add_phone_book">Afegir a l\'agenda</string> <string name="action_delete_contact">Elimina de la llista de contactes</string> <string name="action_block_contact">Bloqueja contacte</string> <string name="action_unblock_contact">Desbloqueja contacte</string> @@ -28,7 +27,6 @@ <string name="minutes_ago">%de minuts abans</string> <string name="unread_conversations">Converses sense llegir o no llegides</string> <string name="sending">enviant…</string> - <string name="encrypted_message">Desxifrant missatge. Espera si us plau…</string> <string name="nick_in_use">El sobrenom ja està en ús</string> <string name="admin">Administrador</string> <string name="owner">Propietari</string> @@ -74,9 +72,7 @@ <string name="clear_conversation_history">Netejar historial de conversa</string> <string name="clear_histor_msg">Vols esborrar tots els missatges d\'aquesta conversa?\n\n<b>Avís:</b> Això no afectarà els missatges desats en altres dispositius o servidors.</string> <string name="delete_messages">Esborrar missatges</string> - <string name="also_end_conversation">Finalitzar aquesta conversa més tard</string> <string name="choose_presence">Selecciona recurs del contacte</string> - <string name="send_plain_text_message">Enviar missatge de text</string> <string name="send_otr_message">Enviar missatge xifrat amb OTR</string> <string name="send_pgp_message">Enviar missatge xifrat amb OpenPGP</string> <string name="your_nick_has_been_changed">El teu sobrenom s\'ha modificat</string> @@ -92,29 +88,23 @@ <string name="contact_has_no_pgp_key">Conversations no ha pogut xifrar els teus missatges perquè el teu contacte no està anunciant la seva clau pública.\n\n<small>Si us plau, demana al teu contacte que configuri OpenPGP.</small></string> <string name="no_pgp_keys">No hi ha claus OPENPGP trobades</string> <string name="contacts_have_no_pgp_keys">Coversations no és possible xifrar les teves converses perquè els teus contactes no han mostrat la seva clau pública.\n\n<small> Si us plau, pregunti als seus contactes per configurar OpenPGP .</small></string> - <string name="encrypted_message_received"><i>Missatge xifrat rebut. Prem per desxifrar i veure-ho.</i></string> <string name="pref_general">General</string> <string name="pref_xmpp_resource">Recursos XMPP</string> <string name="pref_xmpp_resource_summary">El nom que identifica aquest client amb</string> <string name="pref_accept_files">Acceptar fitxers</string> - <string name="pref_accept_files_size_summary">Accepta fitxers automàticament amb una mida menor a…</string> - <string name="pref_notification_settings">Ajustos de notificacions</string> + <string name="pref_accept_files_summary">Accepta fitxers automàticament amb una mida menor a…</string> <string name="pref_notifications">Notificacions</string> <string name="pref_notifications_summary">Notifica quan arriba un nou missatge</string> <string name="pref_vibrate">Vibra</string> <string name="pref_vibrate_summary">Vibra quan arriba un nou missatge</string> <string name="pref_sound">So</string> <string name="pref_sound_summary">Reprodueix el to de trucada amb la notificació</string> - <string name="pref_conference_notifications">Notificacions de conferència</string> - <string name="pref_conference_notifications_summary">Sempre notifica quan arriba un nou missatge de conferència en comptes de només quan està destacat</string> <string name="pref_notification_grace_period">Notificació del període d\'espera</string> <string name="pref_notification_grace_period_summary">Desactiva les notificacions durant un breu termini després de rebre una còpia de missatges carbon</string> - <string name="pref_advanced_options">Opcions avançades</string> <string name="pref_never_send_crash">Mai enviïs informes d\'errors</string> <string name="pref_never_send_crash_summary">Enviant traces d\'execució d\'ajudes al futur desenvolupament del Conversations.</string> <string name="pref_confirm_messages">Confirmant missatges</string> <string name="pref_confirm_messages_summary">Deixeu que el seu contacte sàpiga quan heu rebut i llegit un missatge</string> - <string name="pref_ui_options">Opcions de UI</string> <string name="openpgp_error">OpenKeychain ha reportat un error</string> <string name="error_decrypting_file">I/O Error al desxifrar un arxiu</string> <string name="accept">Acceptar</string> @@ -149,7 +139,6 @@ <string name="account_status_regis_not_sup">El servidor no admet el registre</string> <string name="account_status_security_error">Error de seguretat</string> <string name="account_status_incompatible_server">Servidor incompatible</string> - <string name="encryption_choice_none">Text pla</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> <string name="mgmt_account_edit">Edita compte</string> @@ -170,7 +159,6 @@ <string name="passwords_do_not_match">Contrasenyes no coincideixen</string> <string name="invalid_jid">Aquesta identificació de Jabber no és vàlida</string> <string name="error_out_of_memory">Fora de la capacitat de la mèmoria. L\'imatge és massa gran</string> - <string name="add_phone_book_text">Voleu afegir %s a la teva llista de contactes del telèfon?</string> <string name="contact_status_online">En línia</string> <string name="contact_status_free_to_chat">Lliure per xatejar</string> <string name="contact_status_away">Fora</string> @@ -186,7 +174,6 @@ <string name="server_info_blocking">XEP-0191: Ordre de bloqueix</string> <string name="server_info_roster_version">XEP-0237: Plantilla de les versions</string> <string name="server_info_stream_management">XEP-0198: Gestió de la transmissió</string> - <string name="server_info_pep">XEP-0163: PEP(Avatars)</string> <string name="server_info_available">Disponible</string> <string name="server_info_unavailable">No disponible</string> <string name="missing_public_keys">Clau pública d\'invitació perduda</string> @@ -249,7 +236,6 @@ <string name="skip">Saltar</string> <string name="disable_notifications">Deshabilitar notificacions</string> <string name="disable_notifications_for_this_conversation">Deshabilitar notificacions per aquesta conversació</string> - <string name="notifications_disabled">Les notificacions estàn deshabilitades</string> <string name="enable">Habilitat</string> <string name="conference_requires_password">La sala requereix una contrasenya</string> <string name="enter_password">Introduir-ho la contrasenya</string> @@ -260,12 +246,10 @@ <string name="sure_delete_fingerprint">Estàs segur que t\'agradaria eliminar l\'empremta digital?</string> <string name="ignore">Ignorar</string> <string name="without_mutual_presence_updates"><b>Perill:</b>L\'enviament d\'aquest sense actualitzacions de presència podria causar problemes inesperats.\n\n<small> Ves als detalls del contacte per verificar les subscripcions de presència.</small></string> - <string name="pref_encryption_settings">Configuració del xifratge</string> <string name="pref_force_encryption">Força d\'extrema extrem del xifrat</string> <string name="pref_force_encryption_summary">Enviar sempre missatges xifrat( Excepte per les sales)</string> <string name="pref_dont_save_encrypted">No guardar els misstages xifrats</string> <string name="pref_dont_save_encrypted_summary">Perill: Podria portar a la pèrdua dels missatges</string> - <string name="pref_expert_options">Opcions per a experts</string> <string name="pref_expert_options_summary">Aneu amb cura amb aixó</string> <string name="title_activity_about">Sobre Conversations</string> <string name="pref_about_conversations_summary">Informació sobre la construció i la seva llicència</string> @@ -304,7 +288,6 @@ <string name="verify_otr">Verificar OTR</string> <string name="remote_fingerprint">Empremta digital remota</string> <string name="scan">Escanejar</string> - <string name="or_touch_phones">( o toca altres mòbils)</string> <string name="smp">Protocol de socialistes millionaris</string> <string name="shared_secret_hint">Pista o pregunta</string> <string name="shared_secret_secret">Secret compartit</string> @@ -384,7 +367,6 @@ <string name="public_conference">Comferència de la conversació d\'accés pùblic</string> <string name="private_conference">Privada, únicament els membres de la conferència de conversació</string> <string name="conference_options">Opcions de la sala</string> - <string name="members_only">Privat( Nomès membres)</string> <string name="non_anonymous">Sense anonimat</string> <string name="modified_conference_options">Modificat opcions de la sala!</string> <string name="could_not_modify_conference_options">No s\'ha pogut modificar les opcions de la sala</string> @@ -394,7 +376,6 @@ <string name="two_hours">2 Hores</string> <string name="eight_hours">8 Hores</string> <string name="until_further_notice">Fins nou avís</string> - <string name="pref_input_options">Opcions d\'entrada</string> <string name="pref_enter_is_send">Entra per enviar</string> <string name="pref_enter_is_send_summary">Utilitza el botó enter per enviar el missatge</string> <string name="pref_display_enter_key">Mostra el botó enter</string> @@ -413,7 +394,6 @@ <string name="offering_x_file">Oferint %s</string> <string name="hide_offline">Amaga el fora de línia</string> <string name="disable_account">Deshabilita el compte</string> - <string name="contact_is_typing">%s està escrivint...</string> <string name="contact_has_stopped_typing">%s ha deixat d\'escriure</string> <string name="pref_chat_states">Notificacions d\'escriptura</string> <string name="pref_chat_states_summary">Permet el teu contacte saber quan estàs escrivint un missatge nou</string> @@ -424,7 +404,6 @@ <string name="received_location">Localització rebuda</string> <string name="title_undo_swipe_out_conversation">Conversa tancada</string> <string name="title_undo_swipe_out_muc">S\'ha sortit de la conferència</string> - <string name="pref_certificate_options">Opcions de certificats</string> <string name="pref_dont_trust_system_cas_title">No confiar en les CAs del sistema</string> <string name="pref_dont_trust_system_cas_summary">Tots els certificats han de ser aprovats manualment</string> <string name="pref_remove_trusted_certificates_title">Eliminar certificats</string> diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 816abba8..91fa2464 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Zabezpečená konverzace</string> <string name="action_add_account">Přidat účet</string> <string name="action_edit_contact">Upravit jméno</string> - <string name="action_add_phone_book">Přidat do telefonního seznamu</string> + <string name="action_add_phone_book">Přidat do adresáře</string> <string name="action_delete_contact">Smazat ze seznamu</string> <string name="action_block_contact">Zablokovat kontakt</string> <string name="action_unblock_contact">Odblokovat kontakt</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">před %d minutami</string> <string name="unread_conversations">nepřečtené konverzace</string> <string name="sending">odesílám…</string> - <string name="encrypted_message">Dešifruji zprávu. Chvíli strpení…</string> + <string name="message_decrypting">Dešifrování zprávy. Chvíli strpení...</string> + <string name="pgp_message">OpenPGP šifrovaná zpráva</string> <string name="nick_in_use">Přezdívka se již používá</string> <string name="admin">Administrátor</string> <string name="owner">Vlastník</string> @@ -74,10 +75,12 @@ <string name="clear_conversation_history">Smaže historii konverzací</string> <string name="clear_histor_msg">Chcete smazat všechny zprávy v této konverzaci?\n\n<b>Varování:</b> Toto neovlivní zprávy uložené na jiných přístrojích nebo serverech.</string> <string name="delete_messages">Smazat zprávy</string> - <string name="also_end_conversation">Poté ukončit i tuto konverzaci</string> + <string name="also_end_conversation">Poté ukončit tuto konverzaci</string> <string name="choose_presence">Vybrat aktualizaci stavu pro kontakt</string> - <string name="send_plain_text_message">Poslat textovou zprávu</string> + <string name="send_unencrypted_message">Odeslat nešifrovanou zprávu</string> <string name="send_otr_message">Poslat OTR šifrovanou zprávu</string> + <string name="send_omemo_message">Poslat OMEMO šifrovanou zprávu</string> + <string name="send_omemo_x509_message">Odeslat v\\OMEMO šifrovanou zprávu</string> <string name="send_pgp_message">Poslat OpenPGP šifrovanou zprávu</string> <string name="your_nick_has_been_changed">Přezdívka byla změněna</string> <string name="send_unencrypted">Poslat nešifrované</string> @@ -86,35 +89,34 @@ <string name="openkeychain_required_long">Konverzace využívá aplikaci třetí strany, <b>OpenKeychain</b>, k šifrování a dešifrování zpráv a ke správě veřejných klíčů.\n\nOpenKeychain je licencován pod GPLv3 a dostupný na F-Droid a Google Play.\n\n<small>(Po instalaci prosím restartujte aplikaci Konverzace.)</small></string> <string name="restart">Restartovat</string> <string name="install">Instalovat</string> + <string name="openkeychain_not_installed">Nainstalujte prosím OpenKeychain</string> <string name="offering">nabízí…</string> <string name="waiting">čekám…</string> <string name="no_pgp_key">Nebyl nalezen žádný OpenPGP klíč</string> <string name="contact_has_no_pgp_key">Není možné zašifrovat zprávu v aplikaci Konverzace, protože druhá strana neoznamuje svůj veřejný klíč.\n\n<small>Požádejte svůj kontakt ať si nastaví OpenPGP.</small></string> <string name="no_pgp_keys">Nebyly nalezeny žádné OpenPGP klíče</string> <string name="contacts_have_no_pgp_keys">Není možné zašifrovat zprávy v aplikaci Konverzace, protože kontakty neoznamují svůj veřejný klíč.\n\n<small>Požádejte své kontakty ať si nastaví OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Byla přijata šifrovaná zpráva. Ťukni pro dešifrování a přečtení.</i></string> + <string name="encrypted_message_received"><i>Obdržena šifrovaná zpráva. Dešifrovat dotykem.</i></string> <string name="pref_general">Obecné</string> <string name="pref_xmpp_resource">XMPP zdroj</string> <string name="pref_xmpp_resource_summary">Jméno se kterým se tento klient identifikuje</string> <string name="pref_accept_files">Přijímat soubory</string> - <string name="pref_accept_files_size_summary">Automaticky přijímat soubory menší než…</string> - <string name="pref_notification_settings">Nastavení upozornění</string> + <string name="pref_accept_files_summary">Automaticky přijímat soubory menší než…</string> + <string name="pref_notification_settings">Upozornění</string> <string name="pref_notifications">Upozornění</string> <string name="pref_notifications_summary">Upozornit při přijetí nové zprávy</string> <string name="pref_vibrate">Vibrovat</string> <string name="pref_vibrate_summary">Vibrovat při přijetí nové zprávy</string> <string name="pref_sound">Zvuk</string> <string name="pref_sound_summary">Přehrát zvuk společně s upozorněním</string> - <string name="pref_conference_notifications">Upozornění při konferencích</string> - <string name="pref_conference_notifications_summary">Vždy upozorňovat při nové konferenční zprávě, nejen pokud je vybrána</string> <string name="pref_notification_grace_period">Četnost upozornění</string> <string name="pref_notification_grace_period_summary">Neupozorňovat krátce poté co byla obdržena kopie zprávy</string> - <string name="pref_advanced_options">Pokročilé nastavení</string> + <string name="pref_advanced_options">Rozšířené</string> <string name="pref_never_send_crash">Neodesílat detaily o pádu aplikace</string> <string name="pref_never_send_crash_summary">Zasláním detailů o důvodu selhání pomůžete dalšímu vývoji aplikace Konverzace</string> <string name="pref_confirm_messages">Potvrzovat zprávy</string> <string name="pref_confirm_messages_summary">Oznamovat kontaktům, že zpráva byla přijata a přečtena</string> - <string name="pref_ui_options">Nastavení UI</string> + <string name="pref_ui_options">UI</string> <string name="openpgp_error">OpenKeychain nahlásil chybu</string> <string name="error_decrypting_file">I/O chyba dešifrování souboru</string> <string name="accept">Přijmout</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">Server nepodporuje registrace</string> <string name="account_status_security_error">Bezpečnostní chyba</string> <string name="account_status_incompatible_server">Nekompatibilní server</string> - <string name="encryption_choice_none">Čistý text</string> + <string name="encryption_choice_unencrypted">Nešifrováno</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Upravit účet</string> <string name="mgmt_account_delete">Smazat účet</string> <string name="mgmt_account_disable">Dočasně vypnout</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">Hesla nesouhlasí</string> <string name="invalid_jid">Toto není platné Jabber ID</string> <string name="error_out_of_memory">Nedostatek paměti. Obrázek je příliš velký</string> - <string name="add_phone_book_text">Chcete přidat %s do svého telefonního seznamu?</string> + <string name="add_phone_book_text">Chcete přidat %s do svého adresáře?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">volný pro chat</string> <string name="contact_status_away">pryč</string> @@ -186,7 +189,9 @@ <string name="server_info_blocking">XEP-0191: Příkaz blokování</string> <string name="server_info_roster_version">XEP-0237: Verzování seznamu</string> <string name="server_info_stream_management">XEP-0198: Nastavení proudu</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> + <string name="server_info_push">XEP-0357: Push</string> <string name="server_info_available">dostupný</string> <string name="server_info_unavailable">nedostupný</string> <string name="missing_public_keys">Chybí oznámení o veřejném klíči</string> @@ -204,17 +209,28 @@ <string name="reception_failed">Příjem selhal</string> <string name="your_fingerprint">Váš identifikátor</string> <string name="otr_fingerprint">OTR identifikátor</string> + <string name="omemo_fingerprint">OMEMO otisk</string> + <string name="omemo_fingerprint_x509">v\\OMEMO otisk</string> + <string name="omemo_fingerprint_selected_message">OMEMO otisk zprávy</string> + <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO otisk zprávy</string> + <string name="this_device_omemo_fingerprint">Můj OMEMO otisk</string> + <string name="other_devices">Ostatní přístroje</string> + <string name="trust_omemo_fingerprints">Věřit OMEMO otiskům</string> + <string name="fetching_keys">Získávání klíčů...</string> + <string name="done">Hotovo</string> <string name="verify">Ověřit</string> <string name="decrypt">Dešifrovat</string> <string name="conferences">Konference</string> <string name="search">Hledat</string> <string name="create_contact">Vytvořit kontakt</string> + <string name="enter_contact">Vložit kontakt</string> <string name="join_conference">Připojit ke konferenci</string> <string name="delete_contact">Smazat kontakt</string> <string name="view_contact_details">Zobrazit detaily kontaktu</string> <string name="block_contact">Zablokovat kontakt</string> <string name="unblock_contact">Odblokovat kontakt</string> <string name="create">Vytvořit</string> + <string name="select">Vybrat</string> <string name="contact_already_exists">Kontakt již existuje</string> <string name="join">Vstoupit</string> <string name="conference_address">Adresa konference</string> @@ -225,6 +241,7 @@ <string name="you">Já</string> <string name="action_edit_subject">Upravit jméno konference</string> <string name="conference_not_found">Konference nenalezena</string> + <string name="conference_unknown_error">Došlo k neznámé chybě</string> <string name="leave">Odejít</string> <string name="contact_added_you">Kontakt přidán do seznamu</string> <string name="add_back">Opět přidat</string> @@ -249,7 +266,6 @@ <string name="skip">Přeskočit</string> <string name="disable_notifications">Vypnout upozornění</string> <string name="disable_notifications_for_this_conversation">Vypnout upozornění pro tuto konverzaci</string> - <string name="notifications_disabled">Upozornění jsou vypnuta</string> <string name="enable">Povolit</string> <string name="conference_requires_password">Konference vyžaduje heslo</string> <string name="enter_password">Vložit heslo</string> @@ -260,9 +276,11 @@ <string name="sure_delete_fingerprint">Chcete opravdu smazat tento identifikátor?</string> <string name="ignore">Ignorovat</string> <string name="without_mutual_presence_updates"><b>Varování:</b> Odeslání bez povolení změn stavu může způsobit nečekané problémy na obou stranách.\n\n<small>Přejdi na detaily kontaktu pro ověření povolení o změnách stavu.</small></string> - <string name="pref_encryption_settings">Nastavení šifrování</string> + <string name="pref_security_settings">Zabezpečení</string> <string name="pref_force_encryption">Vynutit šifrování</string> <string name="pref_force_encryption_summary">Vždy zasílat šifrované zprávy (mimo konference)</string> + <string name="pref_allow_message_correction">Povolit opravu zpráv</string> + <string name="pref_allow_message_correction_summary">Povolí kontaktům zpětné upravování jejich zpráv</string> <string name="pref_dont_save_encrypted">Neukládat šifrované zprávy</string> <string name="pref_dont_save_encrypted_summary">Varování: Toto může vést ke ztrátě zpráv</string> <string name="pref_expert_options">Expertní nastavení</string> @@ -283,7 +301,10 @@ <string name="pref_expert_options_other">Další</string> <string name="pref_conference_name">Jméno konference</string> <string name="pref_conference_name_summary">Pro identifikaci konferencí použít téma místnosti místo jejího JID</string> + <string name="pref_autojoin">Automaticky vstupovat do konferencí</string> + <string name="pref_autojoin_summary">Používat autojoin nastavení v záložkách konferencí</string> <string name="toast_message_otr_fingerprint">OTR otisk zkopírován do schránky!</string> + <string name="toast_message_omemo_fingerprint">OMEMO otisk zkopírován do schránky!</string> <string name="conference_banned">Vstup do konference byl zakázán</string> <string name="conference_members_only">Tato konference je pouze pro členy</string> <string name="conference_kicked">Vykopli tě z této konference</string> @@ -307,7 +328,6 @@ <string name="verify_otr">Ověřit OTR</string> <string name="remote_fingerprint">Vzdálený otisk</string> <string name="scan">skenovat</string> - <string name="or_touch_phones">(nebo dotykové telefony)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Nápověda nebo Otázka</string> <string name="shared_secret_secret">Sdílené tajemství</string> @@ -324,6 +344,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Ponechat službu v popředí</string> <string name="pref_keep_foreground_service_summary">Zamezit operačnímu systému v ukončení připojení</string> + <string name="pref_export_logs">Exportovat logy</string> + <string name="pref_export_logs_summary">Zapsat logy na SD kartu</string> + <string name="notification_export_logs_title">Zapisování logů na SD kartu</string> <string name="choose_file">Vybrat soubor</string> <string name="receiving_x_file">Přijímám %1$s (%2$d%% dokončeno)</string> <string name="download_x_file">Stáhnout %s</string> @@ -350,6 +373,17 @@ <string name="reset">Reset</string> <string name="account_image_description">Avatar účtu</string> <string name="copy_otr_clipboard_description">Zkopírovat otisk OTR do schránky</string> + <string name="copy_omemo_clipboard_description">Zkopírovat OMEMO otisk do schránky</string> + <string name="regenerate_omemo_key">Znovu vytvořit OMEMO klíč</string> + <string name="wipe_omemo_pep">Smazat ostatní přístroje z PEP</string> + <string name="clear_other_devices">Smazat přístroje</string> + <string name="clear_other_devices_desc">Opravdu chcete vymazat ostatní přístroje z OMEMO upozornění? Až se příště vaše přístroje připojí, znovu se ohlásí, ale pravděpodobně neobdrží zprávy odeslané v mezičase mezi přihlášeními.</string> + <string name="purge_key">Vymazat klíč</string> + <string name="purge_key_desc_part1">Opravdu si přejete vymazat tento klíč?</string> + <string name="purge_key_desc_part2">Bude neodvolatelně pokládán jako kompromitovaný a již s jeho pomocí nebudete moci nikdy spustit jiné sezení.</string> + <string name="error_no_keys_to_trust_server_error">Pro tento kontakt nejsou dostupné žádné použitelné klíče.\nNahrání klíčů ze serveru nebylo úspěšné. Možná je něco špatně se serverem vašeho kontaktu.</string> + <string name="error_no_keys_to_trust">Pro tento kontakt nejsou dostupné žádné použitelné klíče. Pokud jste smazali jakýkoliv z jeho klíčů bude třeba vygenerovat nové.</string> + <string name="error_trustkeys_title">Chyba</string> <string name="fetching_history_from_server">Načíst historii ze serveru</string> <string name="no_more_history_on_server">Na serveru není žádná další historie</string> <string name="updating">Aktualizuji...</string> @@ -387,8 +421,10 @@ <string name="public_conference">Veřejně přístupná konference</string> <string name="private_conference">Soukromá konference pouze pro členy</string> <string name="conference_options">Nastavení konference</string> - <string name="members_only">Soukromá (pouze členové)</string> + <string name="members_only">Soukromé, pouze pro členy</string> <string name="non_anonymous">Neanonymní</string> + <string name="moderated">Moderováno</string> + <string name="you_are_not_participating">Neúčastníte se</string> <string name="modified_conference_options">Nastavení konference upravena!</string> <string name="could_not_modify_conference_options">Nepodařilo se upravit nastavení konference!</string> <string name="never">Nikdy</string> @@ -397,7 +433,7 @@ <string name="two_hours">2 hodiny</string> <string name="eight_hours">8 hodin</string> <string name="until_further_notice">Než opět změním</string> - <string name="pref_input_options">Nastavení zadávání</string> + <string name="pref_input_options">Vstup</string> <string name="pref_enter_is_send">Enter odesílá</string> <string name="pref_enter_is_send_summary">Použít klávesu enter pro odesílání zpráv</string> <string name="pref_display_enter_key">Zobrazit klávesu enter</string> @@ -427,7 +463,6 @@ <string name="received_location">Přijmout pozici</string> <string name="title_undo_swipe_out_conversation">Conversation zavřena</string> <string name="title_undo_swipe_out_muc">Opustil(a) konferenci</string> - <string name="pref_certificate_options">Nastavení certifikátu</string> <string name="pref_dont_trust_system_cas_title">Nedůvěřovat systémovým CA</string> <string name="pref_dont_trust_system_cas_summary">Všechny certifikáty musí být schváleny ručně</string> <string name="pref_remove_trusted_certificates_title">Odstranit certifikáty</string> @@ -451,6 +486,78 @@ <string name="none">Žádná</string> <string name="recently_used">Naposledy použitá</string> <string name="choose_quick_action">Vybrat rychlou akci</string> - <string name="file_not_found_on_remote_host">Soubor nenalezen na vzdáleném serveru</string> <string name="search_for_contacts_or_groups">Hledat kontakty či skupiny</string> + <string name="send_private_message">Poslat soukromou zprávu</string> + <string name="user_has_left_conference">%s opustil(a) konferenci!</string> + <string name="username">Uživatelské jméno</string> + <string name="username_hint">Uživatelské jméno</string> + <string name="invalid_username">Toto není platné uživatelské jméno</string> + <string name="download_failed_server_not_found">Stahování selhalo: Server nenalezen</string> + <string name="download_failed_file_not_found">Stahování selhalo: Soubor nenalezen</string> + <string name="download_failed_could_not_connect">Stahování selhalo: Nelze se připojit k hostu</string> + <string name="account_status_tor_unavailable">Tor síť není dostupná</string> + <string name="server_info_broken">Rozbité</string> + <string name="pref_presence_settings">Stav</string> + <string name="pref_away_when_screen_off">Pryč při vypnuté obrazovce</string> + <string name="pref_away_when_screen_off_summary">Při vypnuté obrazovce označí váš stav jako pryč</string> + <string name="pref_xa_on_silent_mode">Nedostupný při vypnutém zvuku</string> + <string name="pref_xa_on_silent_mode_summary">Při tichém módu označí váš stav jako nedostupný</string> + <string name="pref_show_connection_options">Rozšířená nastavení připojení</string> + <string name="pref_show_connection_options_summary">Zobrazovat nastavení hostname a port při vytváření účtu</string> + <string name="hostname_example">xmpp.server.cz</string> + <string name="action_add_account_with_certificate">Přidat účet s certifikátem</string> + <string name="unable_to_parse_certificate">Nelze načíst certifikát</string> + <string name="authenticate_with_certificate">Nechat prázdné pro ověření s certifikátem</string> + <string name="mam_prefs">Nastavení archivace</string> + <string name="server_side_mam_prefs">Nastavení archivace na serveru</string> + <string name="fetching_mam_prefs">Získávání nastavení archivace. Chvíli strpení...</string> + <string name="unable_to_fetch_mam_prefs">Nelze získat nastavení archivace</string> + <string name="captcha_ocr">Captcha text</string> + <string name="captcha_required">Captcha vyžadována</string> + <string name="captcha_hint">opište text z obrázku</string> + <string name="certificate_chain_is_not_trusted">Řetězec certifikátů není důvěryhodný</string> + <string name="jid_does_not_match_certificate">Jabber ID neodpovídá certifikátu</string> + <string name="action_renew_certificate">Obnovit certifikát</string> + <string name="error_fetching_omemo_key">Chyba získání OMEMO klíče!</string> + <string name="verified_omemo_key_with_certificate">OMEMO klíč ověřen certifikátem!</string> + <string name="device_does_not_support_certificates">Tento přístroj nepodporuje výběr klientského certifikátu!</string> + <string name="pref_connection_options">Připojení</string> + <string name="account_settings_hostname">Hostname</string> + <string name="account_settings_port">Port</string> + <string name="not_a_valid_port">Toto není platné číslo portu</string> + <string name="not_valid_hostname">Toto není platné hostname</string> + <string name="connected_accounts">%1$d z %2$d účtů připojeno</string> + <plurals name="x_messages"> + <item quantity="one">%d zpráva</item> + <item quantity="few">%d zprávy</item> + <item quantity="other">%d zpráv</item> + </plurals> + <string name="shared_file_with_x">Soubor sdílen s %s</string> + <string name="shared_image_with_x">Obrázek sdílen s %s</string> + <string name="no_storage_permission">Conversations vyžaduje přístup k externímu úložišti</string> + <string name="sync_with_contacts">Synchronizovat s kontakty</string> + <string name="sync_with_contacts_long">Aplikace Conversations by ráda porovnala váš XMPP seznam s vašimi kontakty, aby mohla zobrazit plná jména a avatary.\n\nConversations načte a porovná kontakty pouze lokálně, bez jejich nahrávání na server.\n\nNyní budete dotázáni na udělení práv pro přístup k seznamu kontaktů.</string> + <string name="certificate_information">Informace certifikátu</string> + <string name="certificate_subject">Předmět</string> + <string name="certificate_issuer">Vydavatel</string> + <string name="certificate_cn">Jméno</string> + <string name="certificate_o">Organizace</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(Nedostupné)</string> + <string name="certificate_not_found">Certifikát nenalezen</string> + <string name="notify_on_all_messages">Upozorňovat na všechny zprávy</string> + <string name="notify_only_when_highlighted">Upozorňovat pouze pokud zvýrazněno</string> + <string name="notify_never">Upozornění vypnuta</string> + <string name="notify_paused">Upozornění pozastavena</string> + <string name="pref_picture_compression">Komprimovat obrázky</string> + <string name="pref_picture_compression_summary">Upravit velikost a komprimovat obrázky</string> + <string name="always">Vždy</string> + <string name="automatically">Automaticky</string> + <string name="battery_optimizations_enabled">Povolena optimalizace využití baterie</string> + <string name="battery_optimizations_enabled_explained">Tento přístroj provádí agresivní optimalizace využití baterie pro konverzace. Ty mohou způsobit zpoždění upozornění nebo ztrátu zpráv.\nJe doporučeno tyto vypnout.</string> + <string name="battery_optimizations_enabled_dialog">Tento přístroj provádí agresivní optimalizace využití baterie pro konverzace. Ty mohou způsobit zpoždění upozornění nebo ztrátu zpráv.\nnyní budete vyzváni k jejich vypnutí.</string> + <string name="disable">Vypnout</string> + <string name="selection_too_large">Vybraný obsah je příliš dlouhý</string> + <string name="no_accounts">(Žádné aktivované účty)</string> + <string name="this_field_is_required">Toto pole je vyžadováno</string> </resources> diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index 22d5bc32..f91bf1dd 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -1,102 +1,105 @@ <?xml version='1.0' encoding='UTF-8'?> <resources> - <string name="action_settings">Einstellungen</string> - <string name="action_add">Neue Unterhaltung</string> - <string name="action_accounts">Konten verwalten</string> - <string name="action_end_conversation">Unterhaltung beenden</string> + <string name="action_settings">Einstellungen</string> + <string name="action_add">Neue Unterhaltung</string> + <string name="action_accounts">Konten verwalten</string> + <string name="action_end_conversation">Unterhaltung beenden</string> <string name="action_contact_details">Kontakt-Details</string> <string name="action_muc_details">Konferenz-Details</string> - <string name="action_secure">Verschlüsselte Unterhaltung</string> - <string name="action_add_account">Konto hinzufügen</string> + <string name="action_secure">Verschlüsselte Unterhaltung</string> + <string name="action_add_account">Konto hinzufügen</string> <string name="action_edit_contact">Namen bearbeiten</string> - <string name="action_add_phone_book">Zum Telefonbuch hinzufügen</string> - <string name="action_delete_contact">Aus Kontaktliste entfernen</string> - <string name="action_block_contact">Kontakt sperren</string> - <string name="action_unblock_contact">Kontakt entsperren</string> - <string name="action_block_domain">Domain sperren</string> - <string name="action_unblock_domain">Domain entsperren</string> - <string name="title_activity_manage_accounts">Konten verwalten</string> - <string name="title_activity_settings">Einstellungen</string> + <string name="action_add_phone_book">Zu Telefonbuch hinzufügen</string> + <string name="action_delete_contact">Aus Kontaktliste entfernen</string> + <string name="action_block_contact">Kontakt sperren</string> + <string name="action_unblock_contact">Kontakt entsperren</string> + <string name="action_block_domain">Domain sperren</string> + <string name="action_unblock_domain">Domain entsperren</string> + <string name="title_activity_manage_accounts">Konten verwalten</string> + <string name="title_activity_settings">Einstellungen</string> <string name="title_activity_conference_details">Konferenz-Details</string> <string name="title_activity_contact_details">Kontakt-Details</string> - <string name="title_activity_sharewith">Mit Unterhaltung teilen</string> - <string name="title_activity_start_conversation">Beginne Unterhaltung</string> - <string name="title_activity_choose_contact">Kontakt auswählen</string> - <string name="title_activity_block_list">Sperrliste</string> - <string name="just_now">gerade</string> - <string name="minute_ago">vor einer Minute</string> - <string name="minutes_ago">vor %d Minuten</string> - <string name="unread_conversations">ungelesene Unterhaltungen</string> + <string name="title_activity_sharewith">Mit Unterhaltung teilen</string> + <string name="title_activity_start_conversation">Unterhaltung beginnen</string> + <string name="title_activity_choose_contact">Kontakt auswählen</string> + <string name="title_activity_block_list">Sperrliste</string> + <string name="just_now">gerade</string> + <string name="minute_ago">vor einer Minute</string> + <string name="minutes_ago">vor %d Minuten</string> + <string name="unread_conversations">ungelesene Unterhaltungen</string> <string name="sending">senden…</string> - <string name="encrypted_message">Entschlüssele Nachricht. Bitte warten…</string> - <string name="nick_in_use">Nickname wird bereits verwendet</string> - <string name="admin">Administrator</string> - <string name="owner">Eigentümer</string> - <string name="moderator">Moderator</string> - <string name="participant">Teilnehmer</string> - <string name="visitor">Besucher</string> - <string name="remove_contact_text">Möchtest du %s von deiner Kontaktliste entfernen? Die Unterhaltung mit diesem Kontakt wird dabei nicht entfernt.</string> - <string name="block_contact_text">Möchtest du %s sperren und keine Nachrichten mehr erhalten?</string> - <string name="unblock_contact_text">Möchtest du %s entsperren und wieder Nachrichten empfangen?</string> - <string name="block_domain_text">Sperre alle Kontakte von %s?</string> - <string name="unblock_domain_text">Entsperre alle Kontakte %s?</string> - <string name="contact_blocked">Kontakt gesperrt</string> + <string name="message_decrypting">Nachricht wird entschlüsselt. Bitte warten …</string> + <string name="pgp_message">OpenPGP-verschlüsselte Nachricht</string> + <string name="nick_in_use">Nickname wird bereits verwendet</string> + <string name="admin">Administrator</string> + <string name="owner">Eigentümer</string> + <string name="moderator">Moderator</string> + <string name="participant">Teilnehmer</string> + <string name="visitor">Besucher</string> + <string name="remove_contact_text">Möchtest du %s von deiner Kontaktliste entfernen? Die Unterhaltung mit diesem Kontakt wird dabei nicht entfernt.</string> + <string name="block_contact_text">Möchtest du %s sperren und keine Nachrichten mehr erhalten?</string> + <string name="unblock_contact_text">Möchtest du %s entsperren und wieder Nachrichten empfangen?</string> + <string name="block_domain_text">Alle Kontakte von %s sperren?</string> + <string name="unblock_domain_text">Alle Kontakte von %s entsperren?</string> + <string name="contact_blocked">Kontakt gesperrt</string> <string name="remove_bookmark_text">Möchtest du %s von deiner Kontaktliste entfernen? Die Unterhaltung mit dieser Konferenz wird dabei nicht entfernt.</string> - <string name="register_account">Neues Konto auf dem Server erstellen</string> - <string name="change_password_on_server">Passwort ändern</string> - <string name="share_with">Teile mit…</string> - <string name="start_conversation">Beginne Unterhaltung</string> - <string name="invite_contact">Kontakt einladen</string> - <string name="contacts">Kontakte</string> - <string name="cancel">Abbrechen</string> - <string name="set">Einstellen</string> - <string name="add">Hinzufügen</string> - <string name="edit">Bearbeiten</string> - <string name="delete">Entfernen</string> - <string name="block">Sperren</string> - <string name="unblock">Entsperren</string> - <string name="save">Speichern</string> - <string name="ok">OK</string> - <string name="crash_report_title">Conversations ist abgestürzt</string> - <string name="crash_report_message">Durch das Einsenden von Fehlerberichten hilfst du bei der stetigen Verbesserung von Conversations.\n<b>Achtung:</b> Dies wird eines deiner XMPP-Konten benutzen, um den Entwickler zu kontaktieren.</string> - <string name="send_now">Jetzt abschicken</string> - <string name="send_never">Nie mehr nachfragen</string> - <string name="problem_connecting_to_account">Es gibt Probleme beim Verbindungsaufbau mit einem Konto</string> - <string name="problem_connecting_to_accounts">Es gibt Probleme beim Verbindungsaufbau mit mehreren Konten</string> - <string name="touch_to_fix">Drücke hier, um das Konto zu verwalten</string> - <string name="attach_file">Datei anfügen</string> - <string name="not_in_roster">Der Kontakt ist nicht in deiner Kontaktliste. Möchtest du ihn hinzufügen?</string> - <string name="add_contact">Zur Kontaktliste hinzufügen</string> - <string name="send_failed">Zustellung nicht erfolgreich</string> - <string name="send_rejected">abgelehnt</string> - <string name="preparing_image">Bereite Bild für die Übertragung vor</string> - <string name="action_clear_history">Verlauf löschen</string> + <string name="register_account">Neues Konto auf Server erstellen</string> + <string name="change_password_on_server">Passwort ändern</string> + <string name="share_with">Teilen mit…</string> + <string name="start_conversation">Unterhaltung beginnen</string> + <string name="invite_contact">Kontakt einladen</string> + <string name="contacts">Kontakte</string> + <string name="cancel">Abbrechen</string> + <string name="set">Einstellen</string> + <string name="add">Hinzufügen</string> + <string name="edit">Bearbeiten</string> + <string name="delete">Entfernen</string> + <string name="block">Sperren</string> + <string name="unblock">Entsperren</string> + <string name="save">Speichern</string> + <string name="ok">OK</string> + <string name="crash_report_title">Conversations ist abgestürzt</string> + <string name="crash_report_message">Durch das Einsenden von Fehlerberichten hilfst du bei der stetigen Verbesserung von Conversations.\n<b>Achtung:</b> Dies wird eines deiner XMPP-Konten benutzen, um den Entwickler zu kontaktieren.</string> + <string name="send_now">Jetzt abschicken</string> + <string name="send_never">Nie mehr nachfragen</string> + <string name="problem_connecting_to_account">Es gibt Probleme beim Verbindungsaufbau mit einem Konto</string> + <string name="problem_connecting_to_accounts">Es gibt Probleme beim Verbindungsaufbau mit mehreren Konten</string> + <string name="touch_to_fix">Hier antippen, um Ihre Konten zu verwalten</string> + <string name="attach_file">Datei anfügen</string> + <string name="not_in_roster">Der Kontakt ist nicht in deiner Kontaktliste. Möchtest du ihn hinzufügen?</string> + <string name="add_contact">Kontakt hinzufügen</string> + <string name="send_failed">Zustellung fehlgeschlagen</string> + <string name="send_rejected">abgelehnt</string> + <string name="preparing_image">Bild wird für Übertragung vorbereitet</string> + <string name="action_clear_history">Verlauf löschen</string> <string name="clear_conversation_history">Verlauf löschen</string> - <string name="clear_histor_msg">Möchtest du alle Nachrichten in dieser Unterhaltung löschen?\n\n<b>Achtung:</b> Dies beeinflusst nicht Nachrichten, die auf anderen Geräten oder Servern gespeichert sind.</string> - <string name="delete_messages">Nachrichten löschen</string> - <string name="also_end_conversation">Diese Unterhaltung danach beenden</string> - <string name="choose_presence">Ressource des Kontakts auswählen</string> - <string name="send_plain_text_message">Normal schreiben…</string> + <string name="clear_histor_msg">Möchtest du alle Nachrichten in dieser Unterhaltung löschen?\n\n<b>Achtung:</b> Dies beeinflusst nicht Nachrichten, die auf anderen Geräten oder Servern gespeichert sind.</string> + <string name="delete_messages">Nachrichten löschen</string> + <string name="also_end_conversation">Diese Unterhaltung danach beenden</string> + <string name="choose_presence">Ressource des Kontakts auswählen</string> + <string name="send_unencrypted_message">Normal schreiben…</string> <string name="send_otr_message">OTR-verschlüsselt schreiben…</string> + <string name="send_omemo_message">OMEMO-verschlüsselt schreiben…</string> + <string name="send_omemo_x509_message">v\\OMEMO-verschlüsselte Nachricht senden</string> <string name="send_pgp_message">OpenPGP-verschlüsselt schreiben…</string> - <string name="your_nick_has_been_changed">Dein Nickname wurde geändert</string> - <string name="download_image">Bild herunterladen</string> - <string name="send_unencrypted">Normal verschicken</string> - <string name="decryption_failed">Entschlüsselung fehlgeschlagen. Vielleicht hast du nicht den richtigen privaten Schlüssel.</string> - <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Conversations benutzt eine Drittanwendung namens <b>OpenKeychain</b>, um Nachrichten zu ver- und entschlüsseln und um deine Schlüssel zu verwalten.\n\nOpenKeychain ist GPLv3-lizenziert und kann über F-Droid oder Google Play bezogen werden.\n\n<small>(Bitte starte Conversations danach neu.)</small></string> + <string name="your_nick_has_been_changed">Dein Nickname wurde geändert</string> + <string name="send_unencrypted">Unverschlüsselt senden</string> + <string name="decryption_failed">Entschlüsselung fehlgeschlagen. Vielleicht hast du nicht den richtigen privaten Schlüssel.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations benutzt eine Drittanwendung namens <b>OpenKeychain</b>, um Nachrichten zu ver- und entschlüsseln und um deine Schlüssel zu verwalten.\n\nOpenKeychain ist GPLv3-lizenziert und kann über F-Droid oder Google Play bezogen werden.\n\n<small>(Bitte starte Conversations danach neu.)</small></string> <string name="restart">Neu starten</string> - <string name="install">Installieren</string> + <string name="install">Installieren</string> + <string name="openkeychain_not_installed">Bitte OpenKeychain installieren</string> <string name="offering">angeboten…</string> <string name="waiting">warten…</string> - <string name="no_pgp_key">Kein OpenPGP-Schlüssel gefunden</string> - <string name="contact_has_no_pgp_key">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil dein Kontakt seinen oder ihren Schlüssel nicht preisgibt.\n\n<small>Bitte sag deinem Kontakt, er oder sie möge OpenPGP einrichten.</small></string> - <string name="no_pgp_keys">Keine OpenPGP-Schlüssel gefunden</string> + <string name="no_pgp_key">Kein OpenPGP-Schlüssel gefunden</string> + <string name="contact_has_no_pgp_key">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil dein Kontakt seinen oder ihren Schlüssel nicht preisgibt.\n\n<small>Bitte sag deinem Kontakt, er oder sie möge OpenPGP einrichten.</small></string> + <string name="no_pgp_keys">Keine OpenPGP-Schlüssel gefunden</string> <string name="contacts_have_no_pgp_keys">Conversations ist nicht in der Lage, deine Nachrichten zu verschlüsseln, weil deine Kontakte ihre Schlüssel nicht preisgeben.\n\n<small>Bitte sage deinen Kontakten, sie mögen OpenPGP einrichten.</small></string> - <string name="encrypted_message_received"><i>Verschlüsselte Nachricht erhalten. Drücke hier, um sie zu entschlüsseln und anzuzeigen.</i></string> - <string name="pref_general">Allgemeines</string> - <string name="pref_xmpp_resource">XMPP-Ressource</string> - <string name="pref_xmpp_resource_summary">Der Name, mit dem sich der Client selbst identifiziert</string> + <string name="encrypted_message_received"><i>Verschlüsselte Nachricht erhalten. Antippen zum Entschlüsseln.</i></string> + <string name="pref_general">Allgemeines</string> + <string name="pref_xmpp_resource">XMPP-Ressource</string> + <string name="pref_xmpp_resource_summary">Der Name, mit dem sich der Client selbst identifiziert</string> <string name="pref_accept_files">Dateiannahme</string> <string name="pref_accept_files_summary">Einstellungen für Dateiannahme und automatischen Download</string> <string name="pref_accept_files_size">Größe</string> @@ -105,202 +108,221 @@ <string name="pref_accept_files_download_summary">Automatisches Herunterladen und Akzeptieren von Dateien nur im WLAN</string> <string name="pref_accept_files_download_link">Bilder-Links</string> <string name="pref_accept_files_download_link_summary">Bilder-Links automatisch herunterladen</string> - <string name="pref_notification_settings">Benachrichtigungen</string> - <string name="pref_notifications">Benachrichtigungen</string> - <string name="pref_notifications_summary">Benachrichtige mich, wenn eine neue Nachricht ankommt</string> - <string name="pref_vibrate">Vibrieren</string> - <string name="pref_vibrate_summary">Vibriere, wenn eine neue Nachricht ankommt</string> - <string name="pref_sound">Klingelton</string> - <string name="pref_sound_summary">Spiele Klingelton, wenn eine neue Nachricht ankommt</string> + <string name="pref_notification_settings">Benachrichtigungen</string> + <string name="pref_notifications">Benachrichtigungen</string> + <string name="pref_notifications_summary">Benachrichtigen bei Erhalt einer neuen Nachricht</string> + <string name="pref_vibrate">Vibrieren</string> + <string name="pref_vibrate_summary">Vibrieren bei Erhalt einer neuen Nachricht</string> + <string name="pref_sound">Benachrichtigungston</string> + <string name="pref_sound_summary">Benachrichtigungston wiedergeben</string> <string name="pref_conference_notifications">Konferenz-Benachrichtigungen</string> - <string name="pref_conference_notifications_summary">Benachrichtige mich bei jeder Konferenz-Nachricht und nicht nur, wenn ich angesprochen werde</string> - <string name="pref_notification_grace_period">Gnadenfrist</string> - <string name="pref_notification_grace_period_summary">Deaktiviere Benachrichtigungen für eine kurze Zeit nach Erhalt einer Nachricht, die von einem anderen deiner Clients kommt.</string> - <string name="pref_advanced_options">Erweiterte Optionen</string> - <string name="pref_never_send_crash">Sende niemals Absturzberichte</string> - <string name="pref_never_send_crash_summary">Wenn du Absturzberichte einschickst, hilfst du Conversations stetig zu verbessern</string> + <string name="pref_conference_notifications_summary">Benachrichtige mich bei jeder Konferenz-Nachricht und nicht nur, wenn ich angesprochen werde</string> + <string name="pref_notification_grace_period">Gnadenfrist</string> + <string name="pref_notification_grace_period_summary">Deaktiviere Benachrichtigungen für eine kurze Zeit nach Erhalt einer Nachricht, die von einem anderen deiner Clients kommt.</string> + <string name="pref_advanced_options">Erweiterte Optionen</string> + <string name="pref_never_send_crash">Niemals Absturzberichte senden</string> + <string name="pref_never_send_crash_summary">Wenn du Absturzberichte einschickst, hilfst du Conversations stetig zu verbessern</string> <string name="pref_confirm_messages">Lese- und Empfangsbestätigung senden</string> - <string name="pref_confirm_messages_summary">Informiere deine Kontakte, wenn du eine Nachricht empfangen oder gelesen hast</string> + <string name="pref_confirm_messages_summary">Informiere deine Kontakte, wenn du eine Nachricht empfangen und gelesen hast</string> <string name="pref_confirm_messages_none">Keine Bestätigungen</string> <string name="pref_confirm_messages_received">Nur Empfangsbestätigung</string> <string name="pref_confirm_messages_read_and_received">Lese- und Empfangsbestätigung</string> - <string name="pref_ui_options">Benutzeroberfläche</string> + <string name="pref_ui_options">Benutzeroberfläche</string> <string name="pref_parse_emoticons">Smilies ersetzen</string> <string name="pref_parse_emoticons_summary">Zeige Smilie-Bilder anstelle von Emoticons.</string> - <string name="openpgp_error">Fehler mit OpenKeychain</string> - <string name="error_decrypting_file">Fehler beim Entschlüsseln der Datei</string> - <string name="accept">Annehmen</string> - <string name="error">Ein unbekannter Fehler ist aufgetreten</string> - <string name="pref_grant_presence_updates">Online-Status</string> - <string name="pref_grant_presence_updates_summary">Erlaube neu hinzugefügten Kontakten deinen online-Status zu sehen und frage um Erlaubnis, ihren sehen zu dürfen</string> - <string name="subscriptions">Abonnements</string> - <string name="your_account">Dein Konto</string> - <string name="keys">Schlüssel</string> - <string name="send_presence_updates">Anwesenheitsbenachrichtigungen senden</string> - <string name="receive_presence_updates">Empfange Anwesenheitsbenachrichtigungen</string> - <string name="ask_for_presence_updates">Frage um Erlaubnis, Anwesenheitsbenachrichtigungen sehen zu dürfen</string> + <string name="openpgp_error">Fehler mit OpenKeychain</string> + <string name="error_decrypting_file">Fehler beim Entschlüsseln der Datei</string> + <string name="accept">Annehmen</string> + <string name="error">Ein Fehler ist aufgetreten</string> + <string name="pref_grant_presence_updates">Online-Status</string> + <string name="pref_grant_presence_updates_summary">Erlaube neu hinzugefügten Kontakten meinen Online-Status zu sehen und frage um Erlaubnis, ihren sehen zu dürfen</string> + <string name="subscriptions">Abonnements</string> + <string name="your_account">Dein Konto</string> + <string name="keys">Schlüssel</string> + <string name="send_presence_updates">Online-Status senden</string> + <string name="receive_presence_updates">Online-Status empfangen</string> + <string name="ask_for_presence_updates">Online-Status anfragen</string> <string name="attach_choose_picture">Bild auswählen</string> <string name="attach_take_picture">Bild aufnehmen</string> - <string name="preemptively_grant">Erlaube Statusanfrage vorab</string> - <string name="error_not_an_image_file">Die ausgewählte Datei ist kein Bild</string> - <string name="error_compressing_image">Fehler beim Umwandeln des Bildes</string> - <string name="error_file_not_found">Datei nicht gefunden</string> - <string name="error_io_exception">Allgemeiner Fehler. Vielleicht hast du keinen Speicherplatz mehr?</string> - <string name="error_security_exception_during_image_copy">Die App, mit der du das Bild ausgesucht hast, hat uns keine Rechte eingeräumt, das Bild zu betrachten.\n\n<small>Benutze einen anderen Dateimanager</small></string> - <string name="account_status_unknown">Unbekannt</string> - <string name="account_status_disabled">Vorübergehend abgeschaltet</string> - <string name="account_status_online">Online</string> + <string name="preemptively_grant">Statusanfragen vorab erlauben</string> + <string name="error_not_an_image_file">Die ausgewählte Datei ist kein Bild</string> + <string name="error_compressing_image">Fehler beim Umwandeln des Bildes</string> + <string name="error_file_not_found">Datei nicht gefunden</string> + <string name="error_io_exception">Allgemeiner Fehler. Vielleicht hast du keinen Speicherplatz mehr?</string> + <string name="error_security_exception_during_image_copy">Die App, mit der du das Bild ausgesucht hast, hat uns keine Rechte eingeräumt, das Bild zu betrachten.\n\n<small>Benutze einen anderen Dateimanager</small></string> + <string name="account_status_unknown">Unbekannt</string> + <string name="account_status_disabled">Vorübergehend abgeschaltet</string> + <string name="account_status_online">Online</string> <string name="account_status_connecting">Verbinde…</string> - <string name="account_status_offline">Offline</string> - <string name="account_status_unauthorized">Ungültige Zugangsdaten</string> - <string name="account_status_not_found">Server nicht gefunden</string> - <string name="account_status_no_internet">Keine Internetverbindung</string> - <string name="account_status_regis_fail">Registrierung fehlgeschlagen</string> - <string name="account_status_regis_conflict">Benutzername wird bereits verwendet</string> - <string name="account_status_regis_success">Registrierung abgeschlossen</string> - <string name="account_status_regis_not_sup">Der Server unterstützt keine Registrierung</string> - <string name="account_status_security_error">Sicherheitsfehler</string> - <string name="account_status_incompatible_server">Inkompatibler Server</string> - <string name="encryption_choice_none">Klartext</string> - <string name="encryption_choice_otr">OTR</string> - <string name="encryption_choice_pgp">OpenPGP</string> - <string name="mgmt_account_edit">Konto bearbeiten</string> - <string name="mgmt_account_delete">Löschen</string> - <string name="mgmt_account_disable">Vorübergehend abschalten</string> - <string name="mgmt_account_publish_avatar">Avatar veröffentlichen</string> - <string name="mgmt_account_publish_pgp">Öffentlichen OpenPGP-Schlüssel veröffentlichen</string> - <string name="mgmt_account_enable">Anschalten</string> - <string name="mgmt_account_are_you_sure">Bist du dir sicher?</string> + <string name="account_status_offline">Offline</string> + <string name="account_status_unauthorized">Ungültige Zugangsdaten</string> + <string name="account_status_not_found">Server nicht gefunden</string> + <string name="account_status_no_internet">Keine Internetverbindung</string> + <string name="account_status_regis_fail">Registrierung fehlgeschlagen</string> + <string name="account_status_regis_conflict">Benutzername wird bereits verwendet</string> + <string name="account_status_regis_success">Registrierung abgeschlossen</string> + <string name="account_status_regis_not_sup">Der Server unterstützt keine Registrierung</string> + <string name="account_status_security_error">Sicherheitsfehler</string> + <string name="account_status_incompatible_server">Inkompatibler Server</string> + <string name="encryption_choice_unencrypted">Unverschlüsselt</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Konto bearbeiten</string> + <string name="mgmt_account_delete">Löschen</string> + <string name="mgmt_account_disable">Vorübergehend abschalten</string> + <string name="mgmt_account_publish_avatar">Avatar veröffentlichen</string> + <string name="mgmt_account_publish_pgp">Öffentlichen OpenPGP-Schlüssel veröffentlichen</string> + <string name="mgmt_account_enable">Anschalten</string> + <string name="mgmt_account_are_you_sure">Bist du dir sicher?</string> <string name="mgmt_account_delete_confirm_text">Wenn du dein Konto löschst, gehen alle Gesprächsverläufe verloren</string> - <string name="attach_record_voice">Sprache aufzeichnen</string> + <string name="attach_record_voice">Sprache aufzeichnen</string> <string name="account_settings_jabber_id">Jabber-ID</string> <string name="account_settings_password">Passwort</string> - <string name="account_settings_example_jabber_id">benutzer@domain.de</string> - <string name="account_settings_confirm_password">Passwort bestätigen</string> - <string name="password">Passwort</string> - <string name="confirm_password">Passwort bestätigen</string> - <string name="passwords_do_not_match">Passwörter stimmen nicht überein</string> - <string name="invalid_jid">Ungültige Jabber-ID</string> - <string name="error_out_of_memory">Zu wenig Speicher vorhanden. Das Bild ist zu groß</string> - <string name="add_phone_book_text">Möchtest du %s zum Telefonbuch hinzufügen?</string> + <string name="account_settings_example_jabber_id">benutzer@domain.de</string> + <string name="account_settings_confirm_password">Passwort bestätigen</string> + <string name="password">Passwort</string> + <string name="confirm_password">Passwort bestätigen</string> + <string name="passwords_do_not_match">Passwörter stimmen nicht überein</string> + <string name="invalid_jid">Ungültige Jabber-ID</string> + <string name="error_out_of_memory">Zu wenig Speicher vorhanden. Das Bild ist zu groß</string> + <string name="add_phone_book_text">%s zum Telefonbuch hinzufügen</string> <string name="contact_status_online">online</string> - <string name="contact_status_free_to_chat">Bereit</string> - <string name="contact_status_away">Abwesend</string> - <string name="contact_status_extended_away">Abwesend (erweitert)</string> - <string name="contact_status_do_not_disturb">Nicht stören</string> - <string name="contact_status_offline">Offline</string> - <string name="muc_details_conference">Konferenz</string> - <string name="muc_details_other_members">Andere Mitglieder</string> - <string name="server_info_show_more">Server Info</string> - <string name="server_info_mam">XEP-0313: MAM</string> - <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> + <string name="contact_status_free_to_chat">bereit</string> + <string name="contact_status_away">abwesend</string> + <string name="contact_status_extended_away">länger abwesend</string> + <string name="contact_status_do_not_disturb">nicht stören</string> + <string name="contact_status_offline">offline</string> + <string name="muc_details_conference">Konferenz</string> + <string name="muc_details_other_members">Andere Mitglieder</string> + <string name="server_info_show_more">Server-Info</string> + <string name="server_info_mam">XEP-0313: MAM</string> + <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> <string name="server_info_csi">XEP-0352: CSI</string> - <string name="server_info_blocking">XEP-0191: Blocking Command</string> - <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> - <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatare)</string> + <string name="server_info_blocking">XEP-0191: Blocking Command</string> + <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> + <string name="server_info_stream_management">XEP-0198: Stream Management</string> + <string name="server_info_pep">XEP-0163: PEP (Avatare/OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> + <string name="server_info_push">XEP-0357: Push</string> <string name="server_info_available">ja</string> <string name="server_info_unavailable">nein</string> - <string name="missing_public_keys">Öffentlicher Schlüssel fehlt</string> - <string name="last_seen_now">online</string> - <string name="last_seen_min">vor einer Minute gesehen</string> - <string name="last_seen_mins">vor %d Minuten gesehen</string> - <string name="last_seen_hour">vor einer Stunde gesehen</string> - <string name="last_seen_hours">vor %d Stunden gesehen</string> - <string name="last_seen_day">vor einem Tag gesehen</string> - <string name="last_seen_days">vor %d Tagen gesehen</string> - <string name="never_seen">noch nie gesehen</string> - <string name="install_openkeychain">Verschlüsselte Nachricht. Bitte installiere OpenKeychain zur Entschlüsselung.</string> - <string name="unknown_otr_fingerprint">Unbekannter OTR-Fingerabdruck</string> - <string name="openpgp_messages_found">Verschlüsselte OpenPGP-Nachricht gefunden</string> - <string name="reception_failed">Empfang ist fehlgeschlagen</string> - <string name="your_fingerprint">Dein Fingerabdruck</string> - <string name="otr_fingerprint">OTR-Fingerabdruck</string> - <string name="verify">Verifizieren</string> - <string name="decrypt">Entschlüsseln</string> - <string name="conferences">Konferenzen</string> - <string name="search">Suche</string> - <string name="create_contact">Kontakt erstellen</string> - <string name="join_conference">Konferenz betreten</string> - <string name="delete_contact">Kontakt löschen</string> + <string name="missing_public_keys">Öffentlicher Schlüssel fehlt</string> + <string name="last_seen_now">online</string> + <string name="last_seen_min">vor einer Minute gesehen</string> + <string name="last_seen_mins">vor %d Minuten gesehen</string> + <string name="last_seen_hour">vor einer Stunde gesehen</string> + <string name="last_seen_hours">vor %d Stunden gesehen</string> + <string name="last_seen_day">vor einem Tag gesehen</string> + <string name="last_seen_days">vor %d Tagen gesehen</string> + <string name="never_seen">noch nie gesehen</string> + <string name="install_openkeychain">Verschlüsselte Nachricht. Bitte installiere OpenKeychain zur Entschlüsselung.</string> + <string name="unknown_otr_fingerprint">Unbekannter OTR-Fingerabdruck</string> + <string name="openpgp_messages_found">Verschlüsselte OpenPGP-Nachricht gefunden</string> + <string name="reception_failed">Empfang fehlgeschlagen</string> + <string name="your_fingerprint">Dein Fingerabdruck</string> + <string name="otr_fingerprint">OTR-Fingerabdruck</string> + <string name="omemo_fingerprint">OMEMO-Fingerabdruck</string> + <string name="omemo_fingerprint_x509">v\\OMEMO-Fingerabdruck</string> + <string name="omemo_fingerprint_selected_message">OMEMO-Fingerabdruck der Nachricht</string> + <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO-Fingerabdruck der Nachricht</string> + <string name="this_device_omemo_fingerprint">Eigener OMEMO-Fingerabdruck</string> + <string name="other_devices">Andere Geräte</string> + <string name="trust_omemo_fingerprints">OMEMO-Fingerabdruck vertrauen</string> + <string name="fetching_keys">Schlüssel werden abgerufen …</string> + <string name="done">Erledigt</string> + <string name="verify">Überprüfen</string> + <string name="decrypt">Entschlüsseln</string> + <string name="conferences">Konferenzen</string> + <string name="search">Suchen</string> + <string name="create_contact">Kontakt erstellen</string> + <string name="enter_contact">Kontakt eingeben</string> + <string name="join_conference">Konferenz beitreten</string> + <string name="delete_contact">Kontakt löschen</string> <string name="view_contact_details">Kontakt-Details anzeigen</string> - <string name="block_contact">Kontakt sperren</string> + <string name="block_contact">Kontakt sperren</string> <string name="unblock_contact">Kontakt entsperren</string> - <string name="create">Erstellen</string> - <string name="contact_already_exists">Der Kontakt existiert bereits</string> - <string name="join">Beitreten</string> + <string name="create">Erstellen</string> + <string name="select">Auswählen</string> + <string name="contact_already_exists">Der Kontakt existiert bereits</string> + <string name="join">Beitreten</string> <string name="conference_address">Konferenz-Adresse</string> <string name="conference_address_example">raum@conference.domain.de</string> <string name="save_as_bookmark">Zur Kontaktliste hinzufügen</string> <string name="delete_bookmark">Von Kontaktliste entfernen</string> <string name="bookmark_already_exists">Die Konferenz befindet sich bereits auf deiner Kontaktliste</string> - <string name="you">Du</string> - <string name="action_edit_subject">Konferenz-Thema anpassen</string> - <string name="conference_not_found">Konferenz nicht gefunden</string> - <string name="leave">Verlassen</string> - <string name="contact_added_you">Der Kontakt hat dich zur Kontaktliste hinzugefügt</string> - <string name="add_back">Auch hinzufügen</string> - <string name="contact_has_read_up_to_this_point">%s hat bis zu diesem Punkt gelesen</string> - <string name="publish">Veröffentlichen</string> - <string name="touch_to_choose_picture">Hier klicken, um einen Avatar auszuwählen</string> - <string name="publish_avatar_explanation">Achtung: Jeder, der deinen Status sehen darf, sieht auch deinen Avatar.</string> + <string name="you">Du</string> + <string name="action_edit_subject">Konferenz-Thema bearbeiten</string> + <string name="conference_not_found">Konferenz nicht gefunden</string> + <string name="conference_unknown_error">Unbekannter Fehler</string> + <string name="leave">Verlassen</string> + <string name="contact_added_you">Der Kontakt hat dich zur Kontaktliste hinzugefügt</string> + <string name="add_back">Auch hinzufügen</string> + <string name="contact_has_read_up_to_this_point">%s hat bis zu diesem Punkt gelesen</string> + <string name="publish">Veröffentlichen</string> + <string name="touch_to_choose_picture">Avatar antippen, um Bild aus Galerie auszuwählen</string> + <string name="publish_avatar_explanation">Achtung: Jeder, der deinen Status sehen darf, sieht auch deinen Avatar.</string> <string name="publishing">Veröffentliche…</string> - <string name="error_publish_avatar_server_reject">Der Server hat die Veröffentlichung des Avatars abgelehnt.</string> - <string name="error_publish_avatar_converting">Bei der Konvertierung des Avatars lief etwas schief.</string> - <string name="error_saving_avatar">Kann Avatar nicht speichern.</string> - <string name="or_long_press_for_default">(Oder klicke lange, um Standard wiederherzustellen)</string> - <string name="error_publish_avatar_no_server_support">Dein Server unterstützt die Veröffentlichung von Avataren nicht.</string> + <string name="error_publish_avatar_server_reject">Der Server hat die Veröffentlichung des Avatars abgelehnt.</string> + <string name="error_publish_avatar_converting">Bei der Konvertierung des Avatars lief etwas schief.</string> + <string name="error_saving_avatar">Avatar kann nicht gespeichert werden</string> + <string name="or_long_press_for_default">(Oder klicke lange, um Standard wiederherzustellen)</string> + <string name="error_publish_avatar_no_server_support">Dein Server unterstützt die Veröffentlichung von Avataren nicht.</string> <string name="private_message">private Nachricht:</string> <string name="private_message_to">privat an %s:</string> - <string name="send_private_message_to">Sende private Nachricht an %s…</string> - <string name="connect">Verbinden</string> - <string name="account_already_exists">Das Konto existiert bereits</string> - <string name="next">Weiter</string> + <string name="send_private_message_to">Private Nachricht an %s senden…</string> + <string name="connect">Verbinden</string> + <string name="account_already_exists">Das Konto existiert bereits</string> + <string name="next">Weiter</string> <string name="server_info_session_established">Sitzung wiederhergestellt</string> <string name="additional_information">Zusätzliche Informationen</string> <string name="skip">Überspringen</string> <string name="disable_notifications">Benachrichtigungen deaktivieren</string> <string name="disable_notifications_for_this_conversation">Benachrichtigungen für diese Unterhaltung deaktivieren</string> - <string name="notifications_disabled">Benachrichtigungen sind deaktiviert</string> <string name="enable">Aktivieren</string> <string name="conference_requires_password">Konferenz ist passwortgeschützt</string> <string name="enter_password">Passwort eingeben</string> - <string name="missing_presence_updates">Fehlender Online-Status vom Kontakt</string> + <string name="missing_presence_updates">Fehlender Online-Status des Kontakts</string> <string name="request_presence_updates">Bitte erst Online-Status vom Kontakt anfragen.\n\n<small>Dies wird verwendet, um festzustellen, welche Client(s) der Kontakt benutzt.</small></string> <string name="request_now">Jetzt anfordern</string> <string name="delete_fingerprint">Fingerabdruck löschen</string> - <string name="sure_delete_fingerprint">Soll dieser Fingerabdruck gelöscht werden?</string> + <string name="sure_delete_fingerprint">Soll dieser Fingerabdruck wirklich gelöscht werden?</string> <string name="ignore">Ignorieren</string> - <string name="without_mutual_presence_updates"><b>Achtung:</b> Ohne gegenseitig den Online-Status zu kennen, kann es zu unerwarteten Problemen kommen.\n\n<small>Bitte die Einstellungen in den Kontakt-Details prüfen.</small></string> - <string name="pref_encryption_settings">Verschlüsselungs-Einstellungen</string> + <string name="without_mutual_presence_updates"><b>Achtung:</b> Ohne gegenseitige Kenntnis des Online-Status kann es zu unerwarteten Problemen kommen.\n\n<small>Bitte die Einstellungen in den Kontakt-Details prüfen.</small></string> + <string name="pref_security_settings">Sicherheit</string> <string name="pref_force_encryption">Ende-zu-Ende-Verschlüsselung erzwingen</string> <string name="pref_force_encryption_summary">Nachrichten immer verschlüsseln (außer für Konferenzen)</string> + <string name="pref_allow_message_correction">Nachrichtenkorrektur erlauben</string> + <string name="pref_allow_message_correction_summary">Erlaube Deinen Kontakten das nachträgliche Korrigieren ihrer Nachrichten</string> <string name="pref_dont_save_encrypted">Verschlüsselte Nachrichten nicht speichern</string> <string name="pref_dont_save_encrypted_summary">Achtung: kann zu Nachrichtenverlust führen</string> - <string name="pref_expert_options">Einstellungen für Experten</string> + <string name="pref_expert_options">Experteneinstellungen</string> <string name="pref_expert_options_summary">Hier bitte vorsichtig sein</string> <string name="title_activity_about">Über Conversations</string> <string name="pref_about_conversations_summary">Versions- und Lizenzinformationen</string> <string name="title_pref_quiet_hours">Ruhige Stunden</string> <string name="title_pref_quiet_hours_start_time">Beginn</string> <string name="title_pref_quiet_hours_end_time">Ende</string> - <string name="title_pref_enable_quiet_hours">Aktiviere ruhige Stunden</string> + <string name="title_pref_enable_quiet_hours">Ruhige Stunden aktivieren</string> <string name="pref_quiet_hours_summary">Benachrichtigungen sind während der ruhigen Stunden stumm.</string> <string name="pref_use_larger_font">Schrift vergrößern</string> - <string name="pref_use_larger_font_summary">Größere Schrift verwenden</string> - <string name="pref_use_send_button_to_indicate_status">Absende-Knopf zeigt Online-Status an</string> - <string name="pref_use_indicate_received">Anfrage für Nachrichtenempfang</string> - <string name="pref_use_indicate_received_summary">Empfangene Nachrichten werden mit einem grünen Häkchen markiert. Bitte beachte, dass dies nicht in allen Fällen funktioniert.</string> - <string name="pref_use_send_button_to_indicate_status_summary">Absende-Knopf einfärben, um den Online-Status des Kontakts zu signalisieren</string> - <string name="pref_expert_options_other">Sonstiges</string> - <string name="pref_conference_name">Konferenz-Name</string> - <string name="pref_conference_name_summary">Konferenz-Thema statt Raum-JID als Namen verwenden</string> - <string name="toast_message_otr_fingerprint">OTR-Fingerabdruck in die Zwischenablage kopiert!</string> + <string name="pref_use_larger_font_summary">Größere Schriften für die gesamte App verwenden</string> + <string name="pref_use_send_button_to_indicate_status">\"Senden\"-Schaltfläche zeigt Online-Status an</string> + <string name="pref_use_indicate_received">Empfangsbestätigungen anfragen</string> + <string name="pref_use_indicate_received_summary">Empfangene Nachrichten werden mit einem grünen Häkchen markiert. Bitte beachte, dass dies nicht in allen Fällen funktioniert.</string> + <string name="pref_use_send_button_to_indicate_status_summary">\"Senden\"-Schaltfläche einfärben, um den Online-Status des Kontakts anzuzeigen</string> + <string name="pref_expert_options_other">Sonstiges</string> + <string name="pref_conference_name">Konferenz-Name</string> + <string name="pref_conference_name_summary">Konferenz-Thema statt Raum-JID als Namen verwenden</string> + <string name="pref_autojoin">Unterhaltung automatisch beitreten</string> + <string name="pref_autojoin_summary">Autojoin-Flag in Konferenzlesezeichen beachten</string> + <string name="toast_message_otr_fingerprint">OTR-Fingerabdruck in die Zwischenablage kopiert!</string> + <string name="toast_message_omemo_fingerprint">OMEMO-Fingerabdruck in die Zwischenablage kopiert!</string> <string name="conference_banned">Du wurdest von der Konferenz ausgeschlossen</string> <string name="conference_members_only">Die Konferenz ist nur für Mitglieder</string> <string name="conference_kicked">Du wurdest aus der Konferenz geworfen</string> - <string name="using_account">Verwende Konto %s</string> - <string name="checking_x">%s wird auf HTTP-Host geprüft</string> + <string name="using_account">Verwendetes Konto: %s</string> + <string name="checking_x">%s auf HTTP-Host wird überprüft</string> <string name="not_connected_try_again">Nicht verbunden, bitte später versuchen</string> <string name="check_x_filesize">%s-Größe prüfen</string> <string name="message_options">Nachrichtenoptionen</string> @@ -312,81 +334,94 @@ <string name="url_copied_to_clipboard">URL in Zwischenablage kopiert</string> <string name="message_copied_to_clipboard">Nachricht in Zwischenablage kopiert</string> <string name="image_transmission_failed">Bild-Übertragung fehlgeschlagen</string> - <string name="scan_qr_code">Scanne QR-Code</string> - <string name="show_qr_code">Zeige QR-Code</string> - <string name="show_block_list">Zeige Sperrliste</string> + <string name="scan_qr_code">QR-Code scannen</string> + <string name="show_qr_code">QR-Code anzeigen</string> + <string name="show_block_list">Sperrliste anzeigen</string> <string name="account_details">Konto-Details</string> - <string name="verify_otr">Prüfe OTR</string> - <string name="remote_fingerprint">Fingerabdruck der Gegenseite</string> - <string name="scan">Scanne</string> - <string name="or_touch_phones">(oder Touch-Handys)</string> - <string name="smp">Socialist Millionaire Protocol</string> - <string name="shared_secret_hint">Hinweis oder Frage</string> - <string name="shared_secret_secret">Gemeinsamer Schlüssel</string> - <string name="confirm">Bestätige</string> - <string name="in_progress">In Bearbeitung</string> - <string name="respond">Antworten</string> - <string name="failed">Fehlgeschlagen</string> - <string name="secrets_do_not_match">Schlüssel stimmen nicht überein</string> - <string name="try_again">Erneut versuchen</string> - <string name="finish">Fertig</string> - <string name="verified">Überprüft!</string> + <string name="verify_otr">OTR prüfen</string> + <string name="remote_fingerprint">Fingerabdruck der Gegenseite</string> + <string name="scan">Scannen</string> + <string name="smp">Socialist Millionaire Protocol</string> + <string name="shared_secret_hint">Hinweis oder Frage</string> + <string name="shared_secret_secret">Gemeinsamer Schlüssel</string> + <string name="confirm">Bestätigen</string> + <string name="in_progress">In Bearbeitung</string> + <string name="respond">Antworten</string> + <string name="failed">Fehlgeschlagen</string> + <string name="secrets_do_not_match">Schlüssel stimmen nicht überein</string> + <string name="try_again">Erneut versuchen</string> + <string name="finish">Fertig</string> + <string name="verified">Überprüft!</string> <string name="smp_requested">Kontakt fordert eine Überprüfung an</string> - <string name="no_otr_session_found">Keine gültige OTR Sitzung gefunden!</string> - <string name="conversations_foreground_service">Conversations</string> - <string name="pref_keep_foreground_service">Den Dienst im Vordergrund ausführen.</string> + <string name="no_otr_session_found">Keine gültige OTR-Sitzung gefunden!</string> + <string name="conversations_foreground_service">Conversations</string> + <string name="pref_keep_foreground_service">Dienst im Vordergrund ausführen</string> <string name="pref_keep_foreground_service_summary">Verhindert, dass Android Conversations beendet und die Verbindung unterbricht</string> - <string name="choose_file">Datei auswählen</string> - <string name="receiving_x_file">Empfange %1$s (%2$d%% abgeschlossen)</string> - <string name="download_x_file">Lade %s herunter</string> + <string name="pref_export_logs">Chats exportieren</string> + <string name="pref_export_logs_summary">Chats auf SD-Karte schreiben</string> + <string name="notification_export_logs_title">Chats werden auf SD-Karte geschrieben</string> + <string name="choose_file">Datei auswählen</string> + <string name="receiving_x_file">Empfange %1$s (%2$d%% abgeschlossen)</string> + <string name="download_x_file">%s herunterladen</string> <string name="file">Datei</string> - <string name="open_x_file">Öffne %s</string> - <string name="sending_file">Sende (%1$d%% gesendet)</string> - <string name="preparing_file">Bereite Datei für die Übertragung vor</string> + <string name="open_x_file">%s öffnen</string> + <string name="sending_file">Senden (%1$d%% abgeschlossen)</string> + <string name="preparing_file">Datei wird für die Übertragung vorbereitet</string> <string name="x_file_offered_for_download">%s zum Herunterladen angeboten</string> - <string name="cancel_transmission">Datei-Übertragung abbrechen</string> - <string name="file_transmission_failed">Datei-Übertragung fehlgeschlagen</string> - <string name="file_deleted">Datei wurde gelöscht</string> - <string name="no_application_found_to_open_file">Keine Anwendung zum Öffnen der Datei gefunden</string> - <string name="could_not_verify_fingerprint">Kann Fingerabdruck nicht überprüfen</string> - <string name="manually_verify">Manuell überprüfen</string> - <string name="are_you_sure_verify_fingerprint">Bist du sicher, dass du den OTR-Fingerabdruck des Kontakts überprüfen willst?</string> - <string name="pref_show_dynamic_tags">Dynamische Tags anzeigen</string> - <string name="pref_show_dynamic_tags_summary">Zeige schreibgeschützte Tags unterhalb der Kontakte</string> - <string name="enable_notifications">Aktiviere Benachrichtigungen</string> - <string name="conference_with">Beginne Konferenz mit…</string> - <string name="no_conference_server_found">Konferenz-Server kann nicht gefunden werden</string> - <string name="conference_creation_failed">Beginnen der Konferenz fehlgeschlagen!</string> - <string name="conference_created">Konferenz erstellt!</string> - <string name="secret_accepted">Schlüssel akzeptiert!</string> - <string name="reset">Zurücksetzen</string> - <string name="account_image_description">Konto-Avatar</string> - <string name="copy_otr_clipboard_description">OTR-Fingerabdruck in Zwischenablage kopieren</string> + <string name="cancel_transmission">Übertragung abbrechen</string> + <string name="file_transmission_failed">Übertragung fehlgeschlagen</string> + <string name="file_deleted">Datei wurde gelöscht</string> + <string name="no_application_found_to_open_file">Keine Anwendung zum Öffnen der Datei gefunden</string> + <string name="could_not_verify_fingerprint">Fingerabdruck konnte nicht überprüft werden</string> + <string name="manually_verify">Manuell überprüfen</string> + <string name="are_you_sure_verify_fingerprint">Bist du sicher, dass du den OTR-Fingerabdruck des Kontakts überprüfen willst?</string> + <string name="pref_show_dynamic_tags">Dynamische Tags anzeigen</string> + <string name="pref_show_dynamic_tags_summary">Schreibgeschütze Tags unterhalb der Kontakte anzeigen</string> + <string name="enable_notifications">Benachrichtigungen aktivieren</string> + <string name="conference_with">Konferenz erstellen mit…</string> + <string name="no_conference_server_found">Kein Konferenz-Server gefunden</string> + <string name="conference_creation_failed">Erstellen der Konferenz fehlgeschlagen!</string> + <string name="conference_created">Konferenz erstellt!</string> + <string name="secret_accepted">Schlüssel akzeptiert!</string> + <string name="reset">Zurücksetzen</string> + <string name="account_image_description">Konto-Avatar</string> + <string name="copy_otr_clipboard_description">OTR-Fingerabdruck in Zwischenablage kopieren</string> + <string name="copy_omemo_clipboard_description">OMEMO-Fingerabdruck in Zwischenablage kopieren</string> + <string name="regenerate_omemo_key">OMEMO-Schlüssel erneuern</string> + <string name="wipe_omemo_pep">Andere Geräte von PEP entfernen</string> + <string name="clear_other_devices">Geräte entfernen</string> + <string name="clear_other_devices_desc">Alle anderen Geräte aus der OMEMO-Bekanntmachung entfernen? Die Bekanntmachung wird bei der nächsten Verbindung erneuert, möglicherweise werden aber nicht alle Nachrichten empfangen, die in der Zwischenzeit versendet wurden.</string> + <string name="purge_key">Schlüssel löschen</string> + <string name="purge_key_desc_part1">Soll dieser Schlüssel gelöscht werden?</string> + <string name="purge_key_desc_part2">Dieser Vorgang kann nicht rückgängig gemacht werden und es kann nie wieder eine Verbindung mit diesem Schlüssel hergestellt werden.</string> + <string name="error_no_keys_to_trust_server_error">Für diesen Kontakt sind keine benutzbaren Schlüssel verfügbar.\nNeue Schlüssel vom Server herunterzuladen war nicht erfolgreich. Vielleicht stimmt etwas mit dem Server deines Kontakts nicht.</string> + <string name="error_no_keys_to_trust">Es sind keine Schlüssel für diesen Kontakt verfügbar. Falls sie gelöscht wurden, müssen diese neu erstellt werden.</string> + <string name="error_trustkeys_title">Fehler</string> <string name="fetching_history_from_server">Lade Chatverlauf…</string> <string name="no_more_history_on_server">Keine weiteren Nachrichten vorhanden</string> - <string name="updating">Aktualisiere…</string> - <string name="password_changed">Passwort geändert.</string> - <string name="could_not_change_password">Passwort kann nicht geändert werden.</string> - <string name="otr_session_not_started">Sende eine Nachricht, um eine verschlüsselte Unterhaltung zu beginnen</string> - <string name="ask_question">Frage stellen</string> - <string name="smp_explain_question">Falls du mit deinem Kontakt ein gemeinsames Geheimnis hast (z.B. ein Insider-Witz oder was ihr zuletzt gemeinsam zum Mittag gegessen habt), kann dies zur gegenseitigen Überprüfung des Fingerabdrucks genutzt werden.\n\nDu stellst eine Frage oder gibst einen Hinweis und dein Kontakt gibt eine eindeutige Antwort.</string> - <string name="smp_explain_answer">Dein Kontakt möchte deinen Fingerabdruck mit Hilfe eines gemeinsamen Schlüssels überprüfen. Dein Kontakt hat dazu folgende Frage gestellt.</string> - <string name="shared_secret_hint_should_not_be_empty">Deine Frage darf nicht leer sein.</string> - <string name="shared_secret_can_not_be_empty">Dein gemeinsamer Schlüssel darf nicht leer sein</string> - <string name="manual_verification_explanation">Vergleiche den angezeigten Fingerabdruck sorgfältig mit dem deines Kontakts.\nDu kannst dazu einen sicheren Kommunikationsweg (z.B. verschlüsselte E-Mail oder Telefonanruf) zum Austausch nutzen.</string> - <string name="change_password">Passwort ändern</string> - <string name="current_password">Aktuelles Passwort</string> - <string name="new_password">Neues Passwort</string> - <string name="password_should_not_be_empty">Das Passwort darf nicht leer sein</string> + <string name="updating">Aktualisieren…</string> + <string name="password_changed">Passwort geändert.</string> + <string name="could_not_change_password">Passwort kann nicht geändert werden.</string> + <string name="otr_session_not_started">Sende eine Nachricht, um eine verschlüsselte Unterhaltung zu beginnen</string> + <string name="ask_question">Frage stellen</string> + <string name="smp_explain_question">Falls du mit deinem Kontakt ein gemeinsames Geheimnis hast (z.B. ein Insider-Witz oder was ihr zuletzt gemeinsam zum Mittag gegessen habt), kann dies zur gegenseitigen Überprüfung des Fingerabdrucks genutzt werden.\n\nDu stellst eine Frage oder gibst einen Hinweis und dein Kontakt gibt eine eindeutige Antwort.</string> + <string name="smp_explain_answer">Dein Kontakt möchte deinen Fingerabdruck mit Hilfe eines gemeinsamen Schlüssels überprüfen. Dein Kontakt hat dazu folgende Frage gestellt.</string> + <string name="shared_secret_hint_should_not_be_empty">Deine Frage darf nicht leer sein.</string> + <string name="shared_secret_can_not_be_empty">Dein gemeinsamer Schlüssel darf nicht leer sein</string> + <string name="manual_verification_explanation">Vergleiche den angezeigten Fingerabdruck sorgfältig mit dem deines Kontakts.\nDu kannst dazu einen sicheren Kommunikationsweg (z.B. verschlüsselte E-Mail oder Telefonanruf) zum Austausch nutzen.</string> + <string name="change_password">Passwort ändern</string> + <string name="current_password">Aktuelles Passwort</string> + <string name="new_password">Neues Passwort</string> + <string name="password_should_not_be_empty">Das Passwort darf nicht leer sein</string> <string name="password_should_not_contain_only_spaces">Das Passwort darf nicht nur aus Leerzeichen bestehen</string> - <string name="enable_all_accounts">Alle Konten anschalten</string> - <string name="disable_all_accounts">Alle Konten abschalten</string> - <string name="perform_action_with">Aktion durchführen mit</string> + <string name="enable_all_accounts">Alle Konten anschalten</string> + <string name="disable_all_accounts">Alle Konten abschalten</string> + <string name="perform_action_with">Aktion durchführen mit</string> <string name="no_affiliation">Keine Zugehörigkeit</string> <string name="no_role">Keine Rolle</string> <string name="outcast">Ausgeschlossen</string> <string name="member">Mitglied</string> - <string name="advanced_mode">Experten Modus</string> + <string name="advanced_mode">Erweiterter Modus</string> <string name="grant_membership">Mitgliedschaft gewähren</string> <string name="remove_membership">Mitgliedschaft entziehen</string> <string name="grant_admin_privileges">Administratorrechte gewähren</string> @@ -400,8 +435,10 @@ <string name="public_conference">Öffentlich zugängliche Konferenz</string> <string name="private_conference">Private Konferenz nur für Mitglieder</string> <string name="conference_options">Konferenz-Optionen</string> - <string name="members_only">Privat (Nur für Mitglieder)</string> + <string name="members_only">Privat, nur Mitglieder</string> <string name="non_anonymous">De-anonymisiert</string> + <string name="moderated">Moderiert</string> + <string name="you_are_not_participating">Du bist kein Mitglied</string> <string name="modified_conference_options">Konferenz-Optionen wurden modifiziert!</string> <string name="could_not_modify_conference_options">Konferenz-Optionen konnten nicht modifiziert werden</string> <string name="never">Niemals</string> @@ -409,12 +446,12 @@ <string name="one_hour">1 Stunde</string> <string name="two_hours">2 Stunden</string> <string name="eight_hours">8 Stunden</string> - <string name="until_further_notice">Bis auf weiters</string> - <string name="pref_input_options">Eingabe-Optionen</string> + <string name="until_further_notice">Bis auf Weiteres</string> + <string name="pref_input_options">Eingabe</string> <string name="pref_enter_is_send">Eingabe-Taste (Enter) sendet Nachricht</string> - <string name="pref_enter_is_send_summary">Benutze die Eingabe-Taste (Enter) zum Senden einer Nachricht</string> + <string name="pref_enter_is_send_summary">Eingabe-Taste (Enter) zum Versenden einer Nachricht verwenden</string> <string name="pref_display_enter_key">Zeige Eingabe-Taste (Enter)</string> - <string name="pref_display_enter_key_summary">Zeige die Eingabe-Taste (Enter) anstelle der Smiley-Taste</string> + <string name="pref_display_enter_key_summary">Emoji-Taste durch Eingabe-Taste ersetzen</string> <string name="audio">Audio</string> <string name="video">Video</string> <string name="image">Bild</string> @@ -422,12 +459,12 @@ <string name="apk">Android App</string> <string name="vcard">Kontakt</string> <string name="received_x_file">%s empfangen</string> - <string name="disable_foreground_service">Vordergrund-Dienst beenden</string> - <string name="touch_to_open_conversations">Tippen, um Conversations zu öffnen</string> + <string name="disable_foreground_service">Vordergrund-Dienst deaktivieren</string> + <string name="touch_to_open_conversations">Antippen, um Conversations zu öffnen</string> <string name="avatar_has_been_published">Avatar wurde gespeichert</string> - <string name="sending_x_file">Sende %s</string> - <string name="offering_x_file">%s angeboten</string> - <string name="hide_offline">verstecke offline</string> + <string name="sending_x_file">%s wird gesendet</string> + <string name="offering_x_file">%s wird angeboten</string> + <string name="hide_offline">Offline verstecken</string> <string name="disable_account">Konto abschalten</string> <string name="contact_is_typing">%s schreibt…</string> <string name="contact_has_stopped_typing">%s schreibt nicht mehr</string> @@ -440,8 +477,7 @@ <string name="received_location">Standort empfangen</string> <string name="title_undo_swipe_out_conversation">Unterhaltung beendet</string> <string name="title_undo_swipe_out_muc">Konferenz verlassen</string> - <string name="pref_certificate_options">Zertifikats-Optionen</string> - <string name="pref_dont_trust_system_cas_title">Misstraue Zertifizierungsstellen</string> + <string name="pref_dont_trust_system_cas_title">Zertifizierungsstellen misstrauen</string> <string name="pref_dont_trust_system_cas_summary">Alle Zertifikate müssen manuell bestätigt werden</string> <string name="pref_remove_trusted_certificates_title">Zertifikate löschen</string> <string name="pref_remove_trusted_certificates_summary">Als vertrauenswürdig bestätigte Zertifikate löschen</string> @@ -462,7 +498,6 @@ <string name="none">keine</string> <string name="recently_used">zuletzt verwendet</string> <string name="choose_quick_action">wähle Schnell-Taste</string> - <string name="file_not_found_on_remote_host">Datei auf Server nicht gefunden</string> <string name="search_for_contacts_or_groups">Nach Kontakten oder Konferenzen suchen</string> <string name="pref_led_notification_color">LED-Benachrichtigung Farbe</string> <string name="pref_led_notification_color_summary">Setze die Farbe der LED-Benachrichtigung</string> @@ -512,4 +547,75 @@ <string name="pref_file_transfer_category">Dateiübertragung</string> <string name="cplus_not_copied_to_clipboard_empty">Nichts zu kopieren.</string> <string name="cplus_me">Ich</string> + <string name="send_private_message">Private Nachricht senden</string> + <string name="user_has_left_conference">%s hat die Konferenz verlassen!</string> + <string name="username">Benutzername</string> + <string name="username_hint">Benutzername</string> + <string name="invalid_username">Ungültiger Benutzername</string> + <string name="download_failed_server_not_found">Download fehlgeschlagen: Server nicht gefunden</string> + <string name="download_failed_file_not_found">Download fehlgeschlagen: Datei nicht gefunden</string> + <string name="download_failed_could_not_connect">Download fehlgeschlagen: keine Verbindung zum Host</string> + <string name="account_status_tor_unavailable">Tor-Netzwerk nicht verfügbar</string> + <string name="server_info_broken">Fehlerhaft</string> + <string name="pref_presence_settings">Status</string> + <string name="pref_away_when_screen_off">Abwesend bei abgeschaltetem Bildschirm</string> + <string name="pref_away_when_screen_off_summary">Setzt deinen Status auf \"abwesend\", solange dein Bildschirm abgeschaltet ist</string> + <string name="pref_xa_on_silent_mode">Nicht verfügbar bei Stummschaltung</string> + <string name="pref_xa_on_silent_mode_summary">Setzt deinen Status auf \"nicht verfügbar\", solange dein Telefon lautlos ist</string> + <string name="pref_show_connection_options">Erweiterte Verbindungs-Optionen</string> + <string name="pref_show_connection_options_summary">Hostname- und Port-Optionen bei Kontoeinrichtung anzeigen</string> + <string name="hostname_example">xmpp.domain.de</string> + <string name="action_add_account_with_certificate">Konto mit Zertifikat hinzufügen</string> + <string name="unable_to_parse_certificate">Zertifikat kann nicht gelesen werden</string> + <string name="authenticate_with_certificate">Leer lassen, um mit Zertifikat anzumelden</string> + <string name="mam_prefs">Archivierungseinstellungen</string> + <string name="server_side_mam_prefs">Archivierungseinstellungen des Servers</string> + <string name="fetching_mam_prefs">Archivierungseinstellungen werden abgerufen. Bitte warten …</string> + <string name="unable_to_fetch_mam_prefs">Archivierungseinstellungen konnten nicht abgerufen werden</string> + <string name="captcha_ocr">Captcha Text</string> + <string name="captcha_required">Captcha erforderlich</string> + <string name="captcha_hint">Text aus Captcha eintragen</string> + <string name="certificate_chain_is_not_trusted">Zertifikat wird nicht vertraut</string> + <string name="jid_does_not_match_certificate">Jabber-ID stimmt nicht dem Zertifikat überein</string> + <string name="action_renew_certificate">Zertifikat erneuern</string> + <string name="error_fetching_omemo_key">Kann OMEMO Schlüssel nicht empfangen!</string> + <string name="verified_omemo_key_with_certificate">OMEMO Schlüssel mit Zertifikat bestätigt!</string> + <string name="device_does_not_support_certificates">Dein Gerät unterstützt das Auswählen von Client-Zertifikaten nicht!</string> + <string name="pref_connection_options">Verbindung</string> + <string name="account_settings_hostname">Hostname</string> + <string name="account_settings_port">Port</string> + <string name="not_a_valid_port">Dies ist keine gültige Port-Nummer</string> + <string name="not_valid_hostname">Dies ist kein gültiger Hostname</string> + <string name="connected_accounts">%1$d von %2$d Konten verbunden</string> + <plurals name="x_messages"> + <item quantity="one">%d Nachricht</item> + <item quantity="other">%d Nachrichten</item> + </plurals> + <string name="shared_file_with_x">Datei mit %s geteilt</string> + <string name="shared_image_with_x">Bild mit %s geteilt</string> + <string name="no_storage_permission">Conversations benötigt Zugriff auf externen Speicher</string> + <string name="sync_with_contacts">Mit Kontakten synchronisieren</string> + <string name="sync_with_contacts_long">Conversations möchte deine XMPP-Kontaktliste mit deinen Kontakten abgleichen, um deren vollständige Namen und Avatare anzuzeigen.\n\nConversations wird deine Kontakte nur lokal lesen und abgleichen und überträgt diese nicht auf den Server.\n\nDu wirst nun gefragt, ob du den Zugriff auf deine Kontakte erlauben möchtest.</string> + <string name="certificate_information">Zertifikatinformationen</string> + <string name="certificate_subject">Betreff</string> + <string name="certificate_issuer">Aussteller</string> + <string name="certificate_cn">Name</string> + <string name="certificate_o">Organisation</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(Nicht verfügbar)</string> + <string name="certificate_not_found">Kein Zertifikat gefunden</string> + <string name="notify_on_all_messages">Bei allen Nachrichten benachrichtigen</string> + <string name="notify_only_when_highlighted">Nur benachrichtigen, wenn ich angesprochen werde</string> + <string name="notify_never">Benachrichtigungen deaktiviert</string> + <string name="notify_paused">Benachrichtigungen pausiert</string> + <string name="always">Immer</string> + <string name="automatically">Automatisch</string> + <string name="battery_optimizations_enabled">Batterieoptimierung aktiv</string> + <string name="battery_optimizations_enabled_explained">Dein Telefon wendet Batterioptimierungen bei Conversations an, welche verspätete Benachrichtigungen oder Nachrichtenverlust verursachen können.\nEs ist empfehlenswert diese zu deaktivieren.</string> + <string name="battery_optimizations_enabled_dialog">Dein Telefon wendet Batterioptimierungen bei Conversations an, welche verspätete Benachrichtigungen oder Nachrichtenverlust verursachen können. Es ist empfehlenswert dies zu deaktivieren.</string> + <string name="disable">Deaktivieren</string> + <string name="selection_too_large">Der ausgewählte Bereich ist zu groß</string> + <string name="no_accounts">(Keine aktivierten Konten)</string> + <string name="this_field_is_required">Dieses Feld ist erforderlich</string> + <string name="retry_decryption">Entschlüsselung nochmal versuchen</string> </resources> diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml index 0e2dc010..0bdc6a5e 100644 --- a/src/main/res/values-el/strings.xml +++ b/src/main/res/values-el/strings.xml @@ -9,7 +9,6 @@ <string name="action_secure">Ασφαλής συζήτηση</string> <string name="action_add_account">Προσθήκη λογαριασμού</string> <string name="action_edit_contact">Επεξεργασία ονόματος</string> - <string name="action_add_phone_book">Προσθήκη στον τηλεφωνικό κατάλογο</string> <string name="action_delete_contact">Διαγραφή από τη λίστα επαφών</string> <string name="action_block_contact">Αποκλεισμός επαφής</string> <string name="action_unblock_contact">Άρση αποκλεισμού επαφής</string> @@ -28,7 +27,6 @@ <string name="minutes_ago">πριν από %d λεπτά</string> <string name="unread_conversations">μη αναγνωσμένες Συζητήσεις</string> <string name="sending">αποστολή...</string> - <string name="encrypted_message">Αποκρυπτογράφηση μηνύματος. Παρακαλώ περιμένετε...</string> <string name="nick_in_use">Το ψευδώνυμο είναι ήδη σε χρήση</string> <string name="admin">Διαχειριστής</string> <string name="owner">Κάτοχος</string> @@ -74,9 +72,7 @@ <string name="clear_conversation_history">Καθαρισμός ιστορικού Συζήτησης</string> <string name="clear_histor_msg">Θέλετε να σβήσετε όλα τα μηνύματα αυτής της Συζήτησης;\n\n<b>Προειδοποίηση:</b> Αυτό δεν θα επηρεάσει τα μηνύματα που είναι αποθηκευμένα σε άλλες συσκευές ή άλλους διακομιστές.</string> <string name="delete_messages">Διαγραφή μηνυμάτων</string> - <string name="also_end_conversation">Τερματισμός αυτής της συζήτησης αμέσως μετά</string> <string name="choose_presence">Επιλέξτε παρουσία για επικοινωνία</string> - <string name="send_plain_text_message">Αποστολή απλού μηνύματος κειμένου</string> <string name="send_otr_message">Αποστολή κρυπτογραφημένου μηνύματος OTR</string> <string name="send_pgp_message">Αποστολή κρυπτογραφημένου μηνύματος OpenPGP</string> <string name="your_nick_has_been_changed">Το ψευδώνυμό σας έχει αλλάξει</string> @@ -92,29 +88,23 @@ <string name="contact_has_no_pgp_key">Το Conversations αδυνατεί να κρυπτογραφήσει τα μηνύματά σας γιατί η επαφή σας δεν ανακοινώνει το δημόσιο κλειδί της.\n\n<small>Παρακαλώ ζητήστε από την επαφή σας να εγκαταστήσει το OpenPGP.</small></string> <string name="no_pgp_keys">Δεν βρέθηκαν κλειδιά OpenPGP</string> <string name="contacts_have_no_pgp_keys">Το Conversations αδυνατεί να κρυπτογραφήσει τα μηνύματά σας γιατί οι επαφές σας δεν ανακοινώνουν το δημόσιο κλειδί τους.\n\n<small>Παρακαλώ ζητήστε από τις επαφές σας να εγκαταστήσουν το OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Λήψη κρυπτογραφημένου μηνύματος. Επιλέξτε για ανάγνωση και αποκρυπτογράφηση.</i></string> <string name="pref_general">Γενικά</string> <string name="pref_xmpp_resource">πόρος XMPP</string> <string name="pref_xmpp_resource_summary">Το όνομα με το οποίο ταυτοποιείται αυτό το πρόγραμμα-πελάτης</string> <string name="pref_accept_files">Αποδοχή αρχείων</string> - <string name="pref_accept_files_size_summary">Αυτόματη αποδοχή αρχείων μικρότερα από...</string> - <string name="pref_notification_settings">Επιλογές ειδοποιήσεων</string> + <string name="pref_accept_files_summary">Αυτόματη αποδοχή αρχείων μικρότερα από...</string> <string name="pref_notifications">Ειδοποιήσεις</string> <string name="pref_notifications_summary">Ειδοποίηση όταν λαμβάνεται ένα νέο μήνυμα</string> <string name="pref_vibrate">Δόνηση</string> <string name="pref_vibrate_summary">Επίσης δόνηση όταν έρχεται ένα νέο μήνυμα</string> <string name="pref_sound">Ήχος</string> <string name="pref_sound_summary">Αναπαραγωγή ήχου κλήσης με την ειδοποίηση</string> - <string name="pref_conference_notifications">Ειδοποιήσεις συνδιασκέψεων</string> - <string name="pref_conference_notifications_summary">Ειδοποίηση πάντα όταν ένα νέο μήνυμα συνδιάσκεψης λαμβάνεται αντί για μόνο όταν τονίζεται</string> <string name="pref_notification_grace_period">Περίοδος χάριτος ειδοποιήσεων</string> <string name="pref_notification_grace_period_summary">Απενεργοποίηση ειδοποιήσεων για λίγο χρόνο μετά από τη λήψη ακριβούς αντιγράφου</string> - <string name="pref_advanced_options">Προχωρημένες επιλογές</string> <string name="pref_never_send_crash">Να μην αποστέλλονται αναφορές λαθών</string> <string name="pref_never_send_crash_summary">Στέλνοντας ίχνη στοίβας βοηθάτε την συνεχόμενη ανάπτυξη του Conversations</string> <string name="pref_confirm_messages">Επιβεβαίωση μηνυμάτων</string> <string name="pref_confirm_messages_summary">Επιτρέψτε στην επαφή σας να ειδοποιείται όταν έχετε λάβει και διαβάσει ένα μήνυμα</string> - <string name="pref_ui_options">Επιλογές διεπαφής χρήστη</string> <string name="openpgp_error">Το OpenKeychain ανέφερε κάποιο σφάλμα</string> <string name="error_decrypting_file">Σφάλμα εισόδου/εξόδου κατά την αποκρυπτογράφηση αρχείου</string> <string name="accept">Αποδοχή</string> @@ -149,7 +139,6 @@ <string name="account_status_regis_not_sup">Ο διακομιστής δεν υποστηρίζει εγγραφή</string> <string name="account_status_security_error">Σφάλμα ασφάλειας</string> <string name="account_status_incompatible_server">Μη συμβατός διακομιστής</string> - <string name="encryption_choice_none">Απλό κείμενο</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> <string name="mgmt_account_edit">Επεξεργασία λογαριασμού</string> @@ -170,7 +159,6 @@ <string name="passwords_do_not_match">Τα συνθηματικά δεν ταιριάζουν</string> <string name="invalid_jid">Αυτή δεν είναι έγκυρη ταυτότητα Jabber</string> <string name="error_out_of_memory">Πλήρης μνήμη. Η εικόνα είναι πολύ μεγάλη</string> - <string name="add_phone_book_text">Θέλετε να προσθέσετε την επαφή %s στον τηλεφωνικό κατάλογο του τηλεφώνου σας;</string> <string name="contact_status_online">συνδεμένος</string> <string name="contact_status_free_to_chat">ελεύθερος για συνομιλία</string> <string name="contact_status_away">λείπω</string> @@ -186,7 +174,6 @@ <string name="server_info_blocking">XEP-0191: Εντολή αποκλεισμού</string> <string name="server_info_roster_version">XEP-0237: Διατήρηση εκδόσεων λίστας επαφών</string> <string name="server_info_stream_management">XEP-0198: Διαχείριση ροών</string> - <string name="server_info_pep">XEP-0163: Πρωτόκολλο προσωπικών συμβάντων (εικόνες προφίλ)</string> <string name="server_info_available">διαθέσιμος</string> <string name="server_info_unavailable">μη διαθέσιμος</string> <string name="missing_public_keys">Ελλειπείς ανακοινώσεις δημοσίων κλειδιών</string> @@ -249,7 +236,6 @@ <string name="skip">Παράλειψη</string> <string name="disable_notifications">Απενεργοποίηση ειδοποιήσεων</string> <string name="disable_notifications_for_this_conversation">Απενεργοποίηση ειδοποιήσεων για αυτή την συζήτηση</string> - <string name="notifications_disabled">Οι ειδοποιήσεις είναι απενεργοποιημένες</string> <string name="enable">Ενεργοποίηση</string> <string name="conference_requires_password">Η συζήτηση απαιτεί συνθηματικό</string> <string name="enter_password">Εισαγωγή συνθηματικού</string> @@ -260,12 +246,10 @@ <string name="sure_delete_fingerprint">Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το αποτύπωμα;</string> <string name="ignore">Αγνόηση</string> <string name="without_mutual_presence_updates"><b>Προειδοποίηση:</b> Η αποστολή αυτού χωρίς αμφίδρομες ενημερώσεις παρουσίας μπορεί να προκαλέσει απροσδόκητα προβλήματα.\n\n<small>Πηγαίνετε στις λεπτομέρειες επαφής για να επαληθεύσετε τις συνδρομές παρουσίας σας.</small></string> - <string name="pref_encryption_settings">Ρυθμίσεις κρυπτογράφησης</string> <string name="pref_force_encryption">Επιβολή κρυπτογράφησης από άκρη σε άκρη</string> <string name="pref_force_encryption_summary">Πάντα αποστολή κρυπτογραφημένων μηνυμάτων (εκτός από συνδιασκέψεις)</string> <string name="pref_dont_save_encrypted">Χωρίς αποθήκευση κρυπτογραφημένων μηνυμάτων</string> <string name="pref_dont_save_encrypted_summary">Προειδοποίηση: Αυτό μπορεί να οδηγήσει σε απώλεια μηνυμάτων</string> - <string name="pref_expert_options">Επιλογές για προχωρημένους</string> <string name="pref_expert_options_summary">Παρακαλώ να είστε προσεκτικοί με αυτά</string> <string name="title_activity_about">Σχετικά με το Conversations</string> <string name="pref_about_conversations_summary">Πληροφορίες δημιουργίας και αδειών</string> @@ -304,7 +288,6 @@ <string name="verify_otr">Επαλήθευση OTR</string> <string name="remote_fingerprint">Απομακρυσμένο αποτύπωμα</string> <string name="scan">σάρωση</string> - <string name="or_touch_phones">(ή τηλέφωνα επαφής)</string> <string name="smp">Πρωτόκολλο Socialist Millionaire</string> <string name="shared_secret_hint">Υπαινιγμός ή ερώτηση</string> <string name="shared_secret_secret">Κοινό μυστικό</string> @@ -384,7 +367,6 @@ <string name="public_conference">Συνδιάσκεψη δημόσιας πρόσβασης</string> <string name="private_conference">Ιδιωτική συνδιάσκεψη, μόνο για μέλη</string> <string name="conference_options">Επιλογές συνδιάσκεψης</string> - <string name="members_only">Ιδιωτική (μόνο για μέλη)</string> <string name="non_anonymous">Μη-ανώνυμα</string> <string name="modified_conference_options">Μεταβολή των επιλογών συνδιάσκεψης!</string> <string name="could_not_modify_conference_options">Δεν ήταν δυνατή η μεταβολή των επιλογών συνδιάσκεψης</string> @@ -394,7 +376,6 @@ <string name="two_hours">2 ώρες</string> <string name="eight_hours">8 ώρες</string> <string name="until_further_notice">Μέχρι νεωτέρας</string> - <string name="pref_input_options">Επιλογές εισόδου</string> <string name="pref_enter_is_send">Αποστολή με το πλήκτρο Enter</string> <string name="pref_enter_is_send_summary">Χρήση του πλήκτρου Enter για την αποστολή μηνύματος</string> <string name="pref_display_enter_key">Εμφάνιση του πλήκτρου Enter</string> @@ -413,7 +394,6 @@ <string name="offering_x_file">Προσφορά του %s</string> <string name="hide_offline">Απόκρυψη των εκτός σύνδεσης</string> <string name="disable_account">Απενεργοποίηση λογαριασμού</string> - <string name="contact_is_typing">Ο χρήστης %s γράφει...</string> <string name="contact_has_stopped_typing">Ο χρήστης %s σταμάτησε να γράφει</string> <string name="pref_chat_states">Ειδοποιήσεις πληκτρολόγησης</string> <string name="pref_chat_states_summary">Επιτρέψτε στην επαφή σας να γνωρίζει πότε γράφετε ένα νέο μήνυμα</string> @@ -424,7 +404,6 @@ <string name="received_location">Ελήφθη τοποθεσία</string> <string name="title_undo_swipe_out_conversation">Η συζήτηση έκλεισε</string> <string name="title_undo_swipe_out_muc">Έφυγε από την συνδιάσκεψη</string> - <string name="pref_certificate_options">Επιλογές πιστοποιητικών</string> <string name="pref_dont_trust_system_cas_title">Μη έμπιστες αρχές πιστοποίησης συστήματος</string> <string name="pref_dont_trust_system_cas_summary">Όλα τα πιστοποιητικά πρέπει να εγκριθούν χειροκίνητα</string> <string name="pref_remove_trusted_certificates_title">Αφαίρεση πιστοποιητικών</string> diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 4d8bef38..18d9218d 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Conversación segura</string> <string name="action_add_account">Añadir cuenta</string> <string name="action_edit_contact">Editar contacto</string> - <string name="action_add_phone_book">Añadir a contactos del teléfono</string> + <string name="action_add_phone_book">Añadir a contactos</string> <string name="action_delete_contact">Eliminar contacto de la lista</string> <string name="action_block_contact">Bloquear contacto</string> <string name="action_unblock_contact">Desbloquear contacto</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">hace %d min</string> <string name="unread_conversations">conversaciones por leer</string> <string name="sending">enviando…</string> - <string name="encrypted_message">Descifrando mensaje. Espera por favor…</string> + <string name="message_decrypting">Descifrando mensaje. Por favor, espera...</string> + <string name="pgp_message">Mensaje cifrado con OpenPGP</string> <string name="nick_in_use">El apodo ya está en uso</string> <string name="admin">Administrador</string> <string name="owner">Propietario</string> @@ -76,8 +77,10 @@ <string name="delete_messages">Borrar mensajes</string> <string name="also_end_conversation">Además, terminar esta conversación</string> <string name="choose_presence">Selecciona recurso del contacto</string> - <string name="send_plain_text_message">Enviar mensaje de texto</string> + <string name="send_unencrypted_message">Enviar mensaje sin cifrar</string> <string name="send_otr_message">Enviar mensaje cifrado con OTR</string> + <string name="send_omemo_message">Enviar mensaje cifrado con OMEMO </string> + <string name="send_omemo_x509_message">Enviar mensaje cifrado v\\OMEMO</string> <string name="send_pgp_message">Enviar mensaje cifrado con OpenPGP</string> <string name="your_nick_has_been_changed">Tu apodo se ha modificado</string> <string name="send_unencrypted">Enviar sin cifrar</string> @@ -86,35 +89,34 @@ <string name="openkeychain_required_long">Conversations utiliza una aplicación de terceros llamada <b>OpenKeychain</b> para cifrar y descifrar mensajes y gestionar tus claves públicas.\n\nOpenKeychain está publicado bajo licencia GPLv3 y disponible on F-Droid y Google Play.\n\n<small>(Por favor, reinicie Conversations después.)</small></string> <string name="restart">Reiniciar</string> <string name="install">Instalar</string> + <string name="openkeychain_not_installed">Por favor, instala OpenKeyChain</string> <string name="offering">ofreciendo…</string> <string name="waiting">esperando…</string> <string name="no_pgp_key">Clave OpenPGP no encontrada</string> <string name="contact_has_no_pgp_key">Conversations no ha podido cifrar tus mensajes porque tu contacto no está anunciando su clave publica.\n\n<small>Por favor, pide a tu contacto que configure OpenPGP.</small></string> <string name="no_pgp_keys">Claves OpenPGP no encontradas</string> <string name="contacts_have_no_pgp_keys">Conversations no ha podido cifrar tus mensajes porque tus contactos no están anunciando su clave publica.\n\n<small>Por favor, pide a tus contactos que configuren OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Mensaje cifrado recibido. Pulsa para ver.</i></string> + <string name="encrypted_message_received"><i>Mensaje cifrado recibido. Pulsa para descifrar.</i></string> <string name="pref_general">General</string> <string name="pref_xmpp_resource">Recurso</string> <string name="pref_xmpp_resource_summary">El nombre que identifica el cliente que estás utilizando</string> <string name="pref_accept_files">Aceptar archivos</string> - <string name="pref_accept_files_size_summary">De forma automática aceptar archivos menores que…</string> - <string name="pref_notification_settings">Ajustes de notificación</string> + <string name="pref_accept_files_summary">De forma automática aceptar archivos menores que…</string> + <string name="pref_notification_settings">Notificaciones</string> <string name="pref_notifications">Notificaciones</string> - <string name="pref_notifications_summary">Notifica cuando llega un nuevo mensaje</string> + <string name="pref_notifications_summary">Notificar cuando llega un nuevo mensaje</string> <string name="pref_vibrate">Vibrar</string> <string name="pref_vibrate_summary">Vibra cuando llega un nuevo mensaje</string> <string name="pref_sound">Sonido</string> <string name="pref_sound_summary">Reproduce tono con la notificación</string> - <string name="pref_conference_notifications">Notif. conversación grupo</string> - <string name="pref_conference_notifications_summary">Siempre notifica cuando llega un mensaje a una conversación en grupo y no solo cuando alguien menciona tu nombre en un mensaje</string> <string name="pref_notification_grace_period">Notificaciones Carbons</string> <string name="pref_notification_grace_period_summary">Deshabilita las notificaciones durante un corto periodo de tiempo después de recibir la copia del mensaje carbon</string> - <string name="pref_advanced_options">Opciones avanzadas</string> + <string name="pref_advanced_options">Avanzado</string> <string name="pref_never_send_crash">Nunca informar de errores</string> <string name="pref_never_send_crash_summary">Si envías registros de error ayudas al desarrollo de Conversations</string> - <string name="pref_confirm_messages">Confirmar Mensajes</string> + <string name="pref_confirm_messages">Confirmar mensajes</string> <string name="pref_confirm_messages_summary">Permitir a tus contactos saber cuando recibes y lees un mensaje</string> - <string name="pref_ui_options">Opciones de interfaz</string> + <string name="pref_ui_options">Pantalla</string> <string name="openpgp_error">OpenKeychain reportó un error</string> <string name="error_decrypting_file">Error descifrando archivo</string> <string name="accept">Aceptar</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">El servidor no soporta registros</string> <string name="account_status_security_error">Error de seguridad</string> <string name="account_status_incompatible_server">Servidor incompatible</string> - <string name="encryption_choice_none">Texto plano</string> + <string name="encryption_choice_unencrypted">Sin cifrado</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Editar cuenta</string> <string name="mgmt_account_delete">Eliminar cuenta</string> <string name="mgmt_account_disable">Deshabilitar temporalmente</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">Las contraseñas no coinciden</string> <string name="invalid_jid">El identificador no es un identificador Jabber válido</string> <string name="error_out_of_memory">Sin memoria. La imagen es demasiado grande</string> - <string name="add_phone_book_text">¿Quieres añadir a %s a tus contactos del teléfono?</string> + <string name="add_phone_book_text">¿Quieres añadir a %s a tus contactos?</string> <string name="contact_status_online">Disponible</string> <string name="contact_status_free_to_chat">Hablador</string> <string name="contact_status_away">Ausente</string> @@ -186,7 +189,9 @@ <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> + <string name="server_info_push">XEP-0357: Push</string> <string name="server_info_available">Sí</string> <string name="server_info_unavailable">No</string> <string name="missing_public_keys">Se han perdido las claves de anuncio públicas</string> @@ -204,17 +209,28 @@ <string name="reception_failed">Error al recibir</string> <string name="your_fingerprint">Tu huella digital</string> <string name="otr_fingerprint">Huella digital OTR</string> + <string name="omemo_fingerprint">Huella digital OMEMO</string> + <string name="omemo_fingerprint_x509">Huella digital v\\OMEMO</string> + <string name="omemo_fingerprint_selected_message">Huella digital OMEMO del mensaje</string> + <string name="omemo_fingerprint_x509_selected_message">huella digital v\\OMEMO del mensaje</string> + <string name="this_device_omemo_fingerprint">Tu huella digital OMEMO</string> + <string name="other_devices">Otros dispositivos</string> + <string name="trust_omemo_fingerprints">Huellas digitales OMEMO de confianza</string> + <string name="fetching_keys">Buscando claves...</string> + <string name="done">Hecho</string> <string name="verify">Verificar</string> <string name="decrypt">Descifrar</string> <string name="conferences">Conversación Grupo</string> <string name="search">Buscar</string> <string name="create_contact">Crear Contacto</string> + <string name="enter_contact">Introducir contacto</string> <string name="join_conference">Unirse a Conversación en grupo</string> <string name="delete_contact">Eliminar Contacto</string> <string name="view_contact_details">Ver detalles del contacto</string> <string name="block_contact">Bloquear contacto</string> <string name="unblock_contact">Desbloquear contacto</string> <string name="create">Crear</string> + <string name="select">Seleccionar</string> <string name="contact_already_exists">El contacto ya existe</string> <string name="join">Unirse</string> <string name="conference_address">Dirección</string> @@ -225,6 +241,7 @@ <string name="you">Tú</string> <string name="action_edit_subject">Editar asunto de la conversación</string> <string name="conference_not_found">Conversación no encontrada</string> + <string name="conference_unknown_error">Error desconocido</string> <string name="leave">Salir</string> <string name="contact_added_you">El contacto te ha añadido a su lista de contactos</string> <string name="add_back">Añadir contacto</string> @@ -249,7 +266,6 @@ <string name="skip">Omitir</string> <string name="disable_notifications">Deshabilitar notificaciones</string> <string name="disable_notifications_for_this_conversation">Deshabilitar notificaciones para esta conversación</string> - <string name="notifications_disabled">Las notificaciones están deshabilitadas</string> <string name="enable">Habilitar</string> <string name="conference_requires_password">Esta conversación requiere contraseña</string> <string name="enter_password">Introduce la contraseña</string> @@ -260,12 +276,14 @@ <string name="sure_delete_fingerprint">¿Estás seguro de que quieres eliminar esta huella digital OTR?</string> <string name="ignore">Ignorar</string> <string name="without_mutual_presence_updates"><b>Aviso:</b> Enviando esto sin suscripción de presencia por ambas partes podría causar problemas inesperados.\n\n<small>Verficia la suscripción de presencia en detalles del contacto.</small></string> - <string name="pref_encryption_settings">Ajustes de cifrado</string> + <string name="pref_security_settings">Seguridad</string> <string name="pref_force_encryption">Forzar cifrado end-to-end</string> <string name="pref_force_encryption_summary">Siempre enviar mensajes cifrados (excepto para conversaciones en grupo)</string> + <string name="pref_allow_message_correction">Corrección de mensaje</string> + <string name="pref_allow_message_correction_summary">Permitir a tus contactos editar mensajes previamente enviados</string> <string name="pref_dont_save_encrypted">No guardar mensajes cifrados</string> <string name="pref_dont_save_encrypted_summary">Aviso: Esto podría llevar a pérdida de mensajes</string> - <string name="pref_expert_options">Ajustes avanzados</string> + <string name="pref_expert_options">Opciones para expertos</string> <string name="pref_expert_options_summary">Por favor, cuidado con estas opciones</string> <string name="title_activity_about">Acerca de Conversations</string> <string name="pref_about_conversations_summary">Información de compilación y licencia</string> @@ -283,7 +301,10 @@ <string name="pref_expert_options_other">Otros</string> <string name="pref_conference_name">Nombre conversación grupo</string> <string name="pref_conference_name_summary">Usar el asunto de la conversación en lugar del identificador jabber como nombre en las conversaciones en grupo</string> + <string name="pref_autojoin">Unirse a conversaciones en grupo automáticamente</string> + <string name="pref_autojoin_summary">Respetar la opción de unirse automáticamente de los marcadores de las conversaciones en grupo</string> <string name="toast_message_otr_fingerprint">¡Huella digital OTR copiada al portapapeles!</string> + <string name="toast_message_omemo_fingerprint">¡Huella digital OMEMO copiada al portapapeles!</string> <string name="conference_banned">Tu entrada a esta conversación ha sido prohibida</string> <string name="conference_members_only">Esta conversación es solo para miembros</string> <string name="conference_kicked">Has sido expulsado de esta conversación</string> @@ -307,7 +328,6 @@ <string name="verify_otr">Verificar OTR</string> <string name="remote_fingerprint">Huella digital remota</string> <string name="scan">escanear</string> - <string name="or_touch_phones">(o une los teléfonos)</string> <string name="smp">Protocolo del Socialista Millonario</string> <string name="shared_secret_hint">Sugerencia o pregunta</string> <string name="shared_secret_secret">Secreto compartido</string> @@ -324,6 +344,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Servicio en primer plano</string> <string name="pref_keep_foreground_service_summary">Mantener el servicio en primer plano previene que el sistema cierre la conexión</string> + <string name="pref_export_logs">Exportar logs</string> + <string name="pref_export_logs_summary">Escribir logs en la tarjeta SD</string> + <string name="notification_export_logs_title">Escribiendo logs en la tarjeta SD</string> <string name="choose_file">Seleccionar archivo</string> <string name="receiving_x_file">Recibiendo %1$s (%2$d%% completado)</string> <string name="download_x_file">Descargar %s</string> @@ -340,7 +363,7 @@ <string name="manually_verify">Verificar manualmente</string> <string name="are_you_sure_verify_fingerprint">¿Estás seguro de que quieres verificar el contacto?</string> <string name="pref_show_dynamic_tags">Mostrar etiquetas</string> - <string name="pref_show_dynamic_tags_summary">Muestra información en forma de etiquetas debajo de los contactos</string> + <string name="pref_show_dynamic_tags_summary">Mostrar información en forma de etiquetas debajo de los contactos</string> <string name="enable_notifications">Habilitar notificaciones</string> <string name="conference_with">Crear conversación en grupo</string> <string name="no_conference_server_found">No se ha encontrado el servidor para crear la conversación</string> @@ -350,6 +373,17 @@ <string name="reset">Reinicializar</string> <string name="account_image_description">Imagen de perfil</string> <string name="copy_otr_clipboard_description">Copiar huella digital OTR al portapapeles</string> + <string name="copy_omemo_clipboard_description">Copiar huella digital OMEMO al portapapeles</string> + <string name="regenerate_omemo_key">Regenerar clave OMEMO</string> + <string name="wipe_omemo_pep">Limpiar otros dispositivos de PEP</string> + <string name="clear_other_devices">Limpiar dispositivos</string> + <string name="clear_other_devices_desc">¿Estás seguro de que quieres limpiar todos los otros dispositivos del anuncio OMEMO? La próxima vez que tus dispositivos conecten, tendrán que volver a anunciarse, pero estos podrían no recibir los mensajes enviados durante el proceso.</string> + <string name="purge_key">Eliminar clave</string> + <string name="purge_key_desc_part1">¿Estás seguro de que quieres eliminar esta clave?</string> + <string name="purge_key_desc_part2">Esto será irreversible y nunca podrás iniciar sesión con esta clave de nuevo.</string> + <string name="error_no_keys_to_trust_server_error">No hay claves usables disponibles para este contacto. La búsqueda de nuevas claves al servidor ha fallado. Puede que exista un problema con el servidor de tus contactos.</string> + <string name="error_no_keys_to_trust">No hay claves usables disponibles para este contacto. Si has eliminado alguna de sus claves, tus contactos necesitarán generar nuevas claves.</string> + <string name="error_trustkeys_title">Error</string> <string name="fetching_history_from_server">Buscando historial en el servidor</string> <string name="no_more_history_on_server">No hay más historial en el servidor</string> <string name="updating">Actualizando…</string> @@ -387,8 +421,10 @@ <string name="public_conference">Conversación de acceso público</string> <string name="private_conference">Conversación privada solo para miembros</string> <string name="conference_options">Opciones de conversación</string> - <string name="members_only">Privada (solo miembros)</string> + <string name="members_only">Privada, solo miembros</string> <string name="non_anonymous">No anónima</string> + <string name="moderated">Moderada</string> + <string name="you_are_not_participating">No estás participando</string> <string name="modified_conference_options">¡Modificadas las opciones de la conversación!</string> <string name="could_not_modify_conference_options">No se pueden modificar las opciones de la conversación</string> <string name="never">Nunca</string> @@ -397,11 +433,11 @@ <string name="two_hours">2 horas</string> <string name="eight_hours">8 horas</string> <string name="until_further_notice">Hasta nuevo aviso</string> - <string name="pref_input_options">Opciones de entrada</string> + <string name="pref_input_options">Entrada</string> <string name="pref_enter_is_send">Intro para enviar</string> <string name="pref_enter_is_send_summary">Usar la tecla intro para enviar el mensaje</string> <string name="pref_display_enter_key">Mostrar tecla Intro</string> - <string name="pref_display_enter_key_summary">Cambia la tecla de emoticonos por la tecla Intro</string> + <string name="pref_display_enter_key_summary">Cambiar la tecla de emoticonos por la tecla Intro</string> <string name="audio">audio</string> <string name="video">vídeo</string> <string name="image">imagen</string> @@ -419,7 +455,7 @@ <string name="contact_is_typing">%s está escribiendo...</string> <string name="contact_has_stopped_typing">%s ha dejado de escribir</string> <string name="pref_chat_states">Notificación de escritura</string> - <string name="pref_chat_states_summary">Permite a tus contactos saber cuando estás escribiendo un nuevo mensaje</string> + <string name="pref_chat_states_summary">Permitir a tus contactos saber cuando estás escribiendo un nuevo mensaje</string> <string name="send_location">Enviar ubicación</string> <string name="show_location">Mostrar ubicación</string> <string name="no_application_found_to_display_location">No se ha encontrado ninguna aplicación para mostrar la ubicación</string> @@ -427,13 +463,12 @@ <string name="received_location">Ubicación recibida</string> <string name="title_undo_swipe_out_conversation">Conversación cerrada</string> <string name="title_undo_swipe_out_muc">Has salido de la conversación</string> - <string name="pref_certificate_options">Opciones de Certificados</string> <string name="pref_dont_trust_system_cas_title">No confiar en los CAs del sistema</string> <string name="pref_dont_trust_system_cas_summary">Todos los certificados deben ser aprobados manualmente</string> - <string name="pref_remove_trusted_certificates_title">Eliminar Certificados</string> + <string name="pref_remove_trusted_certificates_title">Eliminar certificados</string> <string name="pref_remove_trusted_certificates_summary">Eliminar manualmente certificados aceptados</string> <string name="toast_no_trusted_certs">No aceptar certificados manualmente</string> - <string name="dialog_manage_certs_title">Eliminar Certificados</string> + <string name="dialog_manage_certs_title">Eliminar certificados</string> <string name="dialog_manage_certs_positivebutton">Eliminar seleccionados</string> <string name="dialog_manage_certs_negativebutton">Cancelar</string> <plurals name="toast_delete_certificates"> @@ -445,10 +480,79 @@ <item quantity="other">Seleccionados %d contactos</item> </plurals> <string name="pref_quick_action_summary">Cambiar el botón de enviar por botón de acción rápida</string> - <string name="pref_quick_action">Acción Rápida</string> + <string name="pref_quick_action">Acción rápida</string> <string name="none">Ninguna</string> <string name="recently_used">Usada más recientemente</string> <string name="choose_quick_action">Elegir acción rápida</string> - <string name="file_not_found_on_remote_host">Archivo no encontrado en servidor remoto</string> <string name="search_for_contacts_or_groups">Buscar contactos o grupos</string> + <string name="send_private_message">Enviar mensaje privado</string> + <string name="user_has_left_conference">¡%s ha dejado la conversación!</string> + <string name="username">Usuario</string> + <string name="username_hint">Usuario</string> + <string name="invalid_username">Esto no es un usuario válido</string> + <string name="download_failed_server_not_found">Error al descargar: Servidor no encontrado</string> + <string name="download_failed_file_not_found">Error al descargar: Archivo no encontrado</string> + <string name="download_failed_could_not_connect">Error al descargar: No se ha podido conectar con el servidor</string> + <string name="account_status_tor_unavailable">Red Tor no disponible.</string> + <string name="server_info_broken">Error</string> + <string name="pref_presence_settings">Presencia</string> + <string name="pref_away_when_screen_off">Ausente con pantalla apagada</string> + <string name="pref_away_when_screen_off_summary">Cambia tu estado a ausente cuando la pantalla está apagada</string> + <string name="pref_xa_on_silent_mode">No disponible en modo silencio</string> + <string name="pref_xa_on_silent_mode_summary">Cambia tu estado a no disponible cuando el dispositivo está en modo silencio</string> + <string name="pref_show_connection_options">Opciones de conexión</string> + <string name="pref_show_connection_options_summary">Mostrar el hostname y el puerto cuando se está creando una cuenta</string> + <string name="hostname_example">xmpp.ejemplo.com</string> + <string name="action_add_account_with_certificate">Añadir cuenta con certificado</string> + <string name="unable_to_parse_certificate">No se ha podido leer el certificado</string> + <string name="authenticate_with_certificate">Dejar vacío para autenticar certificado w/ </string> + <string name="mam_prefs">Preferencias de archivado</string> + <string name="server_side_mam_prefs">Preferencias de archivado en servidor</string> + <string name="fetching_mam_prefs">Buscando preferencias de archivado. Por favor, espera...</string> + <string name="unable_to_fetch_mam_prefs">No se ha podido conseguir las preferencias de archivado</string> + <string name="captcha_ocr">Texto captcha</string> + <string name="captcha_required">Captcha requerido</string> + <string name="captcha_hint">Introduce el texto de la imagen</string> + <string name="certificate_chain_is_not_trusted">La cadena de certificados no es de confianza</string> + <string name="jid_does_not_match_certificate">El identificador Jabber no coincide con el del certificado</string> + <string name="action_renew_certificate">Renovar certificado</string> + <string name="error_fetching_omemo_key">¡Error buscando clave OMEMO!</string> + <string name="verified_omemo_key_with_certificate">¡Clave OMEMO con certificado verificada!</string> + <string name="device_does_not_support_certificates">¡Tu dispositivo no soporta la elección de certificados de cliente!</string> + <string name="pref_connection_options">Conexión</string> + <string name="account_settings_hostname">Hostname</string> + <string name="account_settings_port">Puerto</string> + <string name="not_a_valid_port">Éste no es un número de puerto válido</string> + <string name="not_valid_hostname">Éste no es un hostame válido</string> + <string name="connected_accounts">%1$d de %2$d cuentas conectadas</string> + <plurals name="x_messages"> + <item quantity="one">%d mensaje</item> + <item quantity="other">%d mensajes</item> + </plurals> + <string name="shared_file_with_x">Archivo compartido con %s</string> + <string name="shared_image_with_x">Imagen compartida con %s</string> + <string name="no_storage_permission">Conversations necesita acceder al almacenamiento externo</string> + <string name="sync_with_contacts">Sincronizar contactos</string> + <string name="sync_with_contacts_long">Conversations quiere cruzar tu lista de contactos de XMPP con tus contactos del móvil para mostrar sus nombres completos y sus fotos de perfil.\n\nConversations solo leerá tus contactos y los cruzará localmente sin subirlos a tu servidor.\n\nEl sistema te preguntará ahora para conceder los permisos de acceso a tus contactos del móvil.</string> + <string name="certificate_information">Información de certificado</string> + <string name="certificate_subject">Asunto</string> + <string name="certificate_issuer">Editor</string> + <string name="certificate_cn">Nombre</string> + <string name="certificate_o">Organización</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(No disponible)</string> + <string name="certificate_not_found">Certificado no encontrado</string> + <string name="notify_on_all_messages">Notificar para todos los mensajes</string> + <string name="notify_only_when_highlighted">Notificar solo cuando se recibe un mensjae resaltado</string> + <string name="notify_never">Notificaciones deshabilitadas</string> + <string name="notify_paused">Notificaciones pausadas</string> + <string name="always">Siempre</string> + <string name="automatically">Automáticamente</string> + <string name="battery_optimizations_enabled">Optimizaciones de uso de batería habilitadas</string> + <string name="battery_optimizations_enabled_explained">Tu dispositivo está realizando optimizaciones de uso de batería en Conversations que pueden hacer que los mensajes se retrasen o incluso hacer que se pierdan.\nEs recomendable deshabilitarlas.</string> + <string name="battery_optimizations_enabled_dialog">Tu dispositivo está realizando optimizaciones de uso de batería en Conversations que pueden hacer que los mensajes se retrasen o incluso hacer que se pierdan.\n\nEl sistema te preguntará ahora para deshabilitarlas.</string> + <string name="disable">Deshabilitar</string> + <string name="selection_too_large">El área seleccionada es demasiado grande</string> + <string name="no_accounts">(No hay cuentas activas)</string> + <string name="this_field_is_required">Este campo es requerido</string> </resources> diff --git a/src/main/res/values-eu/strings.xml b/src/main/res/values-eu/strings.xml index ffc276cc..d27c304c 100644 --- a/src/main/res/values-eu/strings.xml +++ b/src/main/res/values-eu/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Elkarrizketa segurua</string> <string name="action_add_account">Kontua gehitu</string> <string name="action_edit_contact">Izena editatu</string> - <string name="action_add_phone_book">Telefono kontaktuetara gehitu</string> + <string name="action_add_phone_book">Helbideen liburura gehitu</string> <string name="action_delete_contact">Zerrendatik ezabatu</string> <string name="action_block_contact">Kontaktua blokeatu</string> <string name="action_unblock_contact">Kontaktua desblokeatu</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d min lehenago</string> <string name="unread_conversations">irakurri gabeko elkarrizketak</string> <string name="sending">bidaltzen…</string> - <string name="encrypted_message">Mezua desenkriptatzen. Mesedez itxaron…</string> + <string name="message_decrypting">Mezua desenkriptatzen. Mesedez itxaron…</string> + <string name="pgp_message">OpenPGPz enkriptatutako mezua</string> <string name="nick_in_use">Ezizena erabilita dagoeneko</string> <string name="admin">Administratzailea</string> <string name="owner">Jabea</string> @@ -76,8 +77,10 @@ <string name="delete_messages">Mezuak ezabatu</string> <string name="also_end_conversation">Elkarrizketa hau jarraian amaitu</string> <string name="choose_presence">Hautatu agerpena kontaktuarentzat</string> - <string name="send_plain_text_message">Testu mezua bidali</string> + <string name="send_unencrypted_message">Enkriptatu gabeko mezua bidali</string> <string name="send_otr_message">OTRz enkriptatutako mezua bidali</string> + <string name="send_omemo_message">OMEMOz enkriptatutako mezua bidali</string> + <string name="send_omemo_x509_message">v\\OMEMOz enkriptatutako mezua bidali</string> <string name="send_pgp_message">OpenPGPz enkriptatutako mezua bidali</string> <string name="your_nick_has_been_changed">Zure ezizena aldatu da</string> <string name="send_unencrypted">Enkriptatu gabe bidali</string> @@ -86,35 +89,34 @@ <string name="openkeychain_required_long">Conversationsek <b>OpenKeychain</b> izeneko hirugarren app bat erabiltzen du mezuak enkriptatu eta desenkriptatzeko eta zure gako publikoak kudeatzeko.\n\nOpenKeychain GPLv3 lizentziapean dago eta F-Droid eta Google Playn eskura daiteke.\n\n<small>(Mesedez ondoren Conversations berrabiarazi)</small></string> <string name="restart">Berrabiarazi</string> <string name="install">Instalatu</string> + <string name="openkeychain_not_installed">Mesedez instalatu ezazu OpenKeychain</string> <string name="offering">eskeintzen…</string> <string name="waiting">itxaroten…</string> <string name="no_pgp_key">Ez da OpenPGP gakorik aurkitu</string> <string name="contact_has_no_pgp_key">Conversations ez da zure mezuak enkriptatzeko gai zure kontaktua bere gako publikoa jakinarazten ez dagoelako.\n\n<small>Mesedez eskatu ezaiozu zure kontaktuari openPGP konfigura dezan.</small></string> <string name="no_pgp_keys">Ez da OpenPGP gakorik aurkitu</string> <string name="contacts_have_no_pgp_keys">Conversations ez da zure mezuak enkriptatzeko gai zure kontaktuak haien gako publikoa jakinarazten ez daudelako.\n\n<small>Mesedez eskatu ezaiezu zure kontakuei OpenPGP konfigura dezaten.</small></string> - <string name="encrypted_message_received"><i>Enkriptatutako mezua jaso da. Ukitu ikusi eta desenkriptatzeko.</i></string> + <string name="encrypted_message_received"><i>Enkriptatutako mezua jaso da. Ukitu desenkriptatzeko.</i></string> <string name="pref_general">Orokorrak</string> <string name="pref_xmpp_resource">XMPP baliabidea</string> <string name="pref_xmpp_resource_summary">Bezero honek bere burua aurkezteko erabiltzen duen izena</string> <string name="pref_accept_files">Fitxategiak onartu</string> - <string name="pref_accept_files_size_summary">Hurrengo tamaina baino fitxategi txikiagoak automatikoki onartu…</string> - <string name="pref_notification_settings">Jakinarazpenen ezarpenak</string> + <string name="pref_accept_files_summary">Hurrengo tamaina baino fitxategi txikiagoak automatikoki onartu…</string> + <string name="pref_notification_settings">Jakinarazpena</string> <string name="pref_notifications">Jakinarazpenak</string> <string name="pref_notifications_summary">Mezu berri bat heltzerakoan jakinarazi</string> <string name="pref_vibrate">Dardaratu</string> <string name="pref_vibrate_summary">Dardaratu ere mezu berri bat heltzerakoan</string> <string name="pref_sound">Soinua</string> <string name="pref_sound_summary">Dei-tonua jo jakinarazpenarekin</string> - <string name="pref_conference_notifications">Konferentzien jakinarazpenak</string> - <string name="pref_conference_notifications_summary">Beti jakinarazi konferentzia mezu berri bat heltzerakoan eta ez soilik nabarmentzerakoan</string> <string name="pref_notification_grace_period">Jakinarazpenen grazia epea</string> <string name="pref_notification_grace_period_summary">Jakinarazpenak denbora labur baterako ezgaitu ikatz-kopia bat jaso ondoren</string> - <string name="pref_advanced_options">Aukera aurreratuak</string> + <string name="pref_advanced_options">Aurreratua</string> <string name="pref_never_send_crash">Gelditze txostenik ez bidali inoiz</string> <string name="pref_never_send_crash_summary">Akats harraskak bidaliz Conversationsen garapenean laguntzen duzu</string> <string name="pref_confirm_messages">Mezuak egiaztatu</string> <string name="pref_confirm_messages_summary">Zure kontaktuak mezu bat noiz jaso eta irakurri duzun jakin dezan baimendu</string> - <string name="pref_ui_options">Erabiltzaile-interfazearen aukerak</string> + <string name="pref_ui_options">Erabiltzaile-interfazea</string> <string name="openpgp_error">OpenKeychainek akats baten berri eman du</string> <string name="error_decrypting_file">Sarrera/Irteera akatsa fitxategia desenkriptatzerakoan</string> <string name="accept">Onartu</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">Zerbitzariak ez du erregistratzea onartzen</string> <string name="account_status_security_error">Segurtasun akatsa</string> <string name="account_status_incompatible_server">Zerbitzari ez bateragarria</string> - <string name="encryption_choice_none">Testu laua</string> + <string name="encryption_choice_unencrypted">Enkriptatu gabe</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Kontua editatu</string> <string name="mgmt_account_delete">Kontua ezabatu</string> <string name="mgmt_account_disable">Aldi baterako ezgaitu</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">Pasahitzak ez dute bat egiten</string> <string name="invalid_jid">Hau ez da Jabber ID baliodun bat</string> <string name="error_out_of_memory">Memoriarik gabe. Irudia handiegia da</string> - <string name="add_phone_book_text">%s zure telefono kontaktu zerrendara gehitu nahi al duzu?</string> + <string name="add_phone_book_text">%s zure helbideen liburura gehitu nahi duzu?</string> <string name="contact_status_online">konektatuta</string> <string name="contact_status_free_to_chat">hitzegiteko aske</string> <string name="contact_status_away">kanpoan</string> @@ -186,7 +189,9 @@ <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Profileko argazkiak)</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> + <string name="server_info_push">XEP-0357: Push</string> <string name="server_info_available">eskuragarri</string> <string name="server_info_unavailable">ez eskuragarri</string> <string name="missing_public_keys">Gako publikoen iragarpenak faltan</string> @@ -204,17 +209,28 @@ <string name="reception_failed">Jasotzeak huts egin du</string> <string name="your_fingerprint">Zure hatz-marka</string> <string name="otr_fingerprint">OTR hatz-marka</string> + <string name="omemo_fingerprint">OMEMO hatz-marka</string> + <string name="omemo_fingerprint_x509">v\\OMEMO hatz-marka</string> + <string name="omemo_fingerprint_selected_message">Mezuaren OMEMO hatz-marka</string> + <string name="omemo_fingerprint_x509_selected_message">Mezuaren v\\OMEMO hatz-marka</string> + <string name="this_device_omemo_fingerprint">Norberaren OMEMO hatz-marka</string> + <string name="other_devices">Beste gailuak</string> + <string name="trust_omemo_fingerprints">OMEMO hatz-marketaz fidatu</string> + <string name="fetching_keys">Gakoak eskuratzen...</string> + <string name="done">Eginda</string> <string name="verify">Egiaztatu</string> <string name="decrypt">Desenkriptatu</string> <string name="conferences">Konferentziak</string> <string name="search">Bilatu</string> <string name="create_contact">Kontaktua sortu</string> + <string name="enter_contact">Kontaktua sartu</string> <string name="join_conference">Konferentziara batu</string> <string name="delete_contact">Kontaktua ezabatu</string> <string name="view_contact_details">Kontaktuaren xehetasunak ikusi</string> <string name="block_contact">Kontaktua blokeatu</string> <string name="unblock_contact">Kontaktua desblokeatu</string> <string name="create">Sortu</string> + <string name="select">Hautatu</string> <string name="contact_already_exists">Kontaktua existitzen da dagoeneko</string> <string name="join">Batu</string> <string name="conference_address">Konferentziaren helbidea</string> @@ -225,6 +241,7 @@ <string name="you">Zu</string> <string name="action_edit_subject">Konferentziaren gaia editatu</string> <string name="conference_not_found">Konferentzia ez da aurkitu</string> + <string name="conference_unknown_error">Akats ezezaguna jaso da</string> <string name="leave">Alde egin</string> <string name="contact_added_you">Kontaktuak bere zerrendara gehitu zaitu</string> <string name="add_back">Bera gehitu</string> @@ -249,7 +266,6 @@ <string name="skip">Orain ez</string> <string name="disable_notifications">Jakinarazpenak ezgaitu</string> <string name="disable_notifications_for_this_conversation">Elkarrizketa honetarako jakinarazpenak ezgaitu</string> - <string name="notifications_disabled">Jakinarazpenak ezgaituta daude</string> <string name="enable">Gaitu</string> <string name="conference_requires_password">Konferentziak pasahitza behar du</string> <string name="enter_password">Sartu pasahitza</string> @@ -260,12 +276,14 @@ <string name="sure_delete_fingerprint">Ziur al zaude hatz-marka hau ezabatu nahi duzulaz?</string> <string name="ignore">Kasurik ez egin</string> <string name="without_mutual_presence_updates"><b>Adi:</b> Bien arteko presentzia eguneraketarik gabe hau bidaltzeak ustekabeko arazoak sor litzake.\n\n<small>Joan zaitez kontaktuaren xehetasunetara zure presentzia eguneraketak egiaztatzeko.</small></string> - <string name="pref_encryption_settings">Enkriptazio ezarpenak</string> + <string name="pref_security_settings">Segurtasuna</string> <string name="pref_force_encryption">End-to-end enkriptazioa behartu</string> <string name="pref_force_encryption_summary">Mezuak beti enkriptatuta bidali (konferentzietan izan ezik)</string> + <string name="pref_allow_message_correction">Mezuen zuzenketa baimendu</string> + <string name="pref_allow_message_correction_summary">Zure kontaktuak haien mezuak atzeraeraginez editatzea baimendu</string> <string name="pref_dont_save_encrypted">Ez gorde enkriptatutako mezuak</string> <string name="pref_dont_save_encrypted_summary">Adi: Honek mezuen galera ekar lezake</string> - <string name="pref_expert_options">Adituentzako aukerak</string> + <string name="pref_expert_options">Adituentzako ezarpenak</string> <string name="pref_expert_options_summary">Mesedez kontuz ibili hauekin</string> <string name="title_activity_about">Conversationsi buruz</string> <string name="pref_about_conversations_summary">Eraikitze eta lizentzia informazioa</string> @@ -283,7 +301,10 @@ <string name="pref_expert_options_other">Besteak</string> <string name="pref_conference_name">Konferentziaren izena</string> <string name="pref_conference_name_summary">Erabili gelaren gaia konferentziak identifikatzeko eta ez JIDa</string> + <string name="pref_autojoin">Konferentzietara automatikoki batu</string> + <string name="pref_autojoin_summary">Automatikoki batzeko marka errespetatu konferentzien laster-marketan</string> <string name="toast_message_otr_fingerprint">OTR hatz-marka arbelara kopiatu da</string> + <string name="toast_message_omemo_fingerprint">OMEMO hatz-marka arbelara kopiatu da</string> <string name="conference_banned">Konferentzia honetara sartzea debekatuta duzu</string> <string name="conference_members_only">Konferentzia hau kideentzat da soilik</string> <string name="conference_kicked">Konferentzia honetatik kanporatua izan zara</string> @@ -307,7 +328,6 @@ <string name="verify_otr">OTR egiaztatu</string> <string name="remote_fingerprint">Urruneko hatz-marka</string> <string name="scan">eskaneatu</string> - <string name="or_touch_phones">(edo telefonoak ikutu)</string> <string name="smp">Socialist Millionaire protokoloa</string> <string name="shared_secret_hint">Iradokizuna edo galdera</string> <string name="shared_secret_secret">Partekatutako sekretua</string> @@ -324,6 +344,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Zerbitzua atzeko planoan mantendu</string> <string name="pref_keep_foreground_service_summary">Sistema eragileak zure konexioa hiltzea galarazten du</string> + <string name="pref_export_logs">Erregistroak esportatu</string> + <string name="pref_export_logs_summary">Erregistroak SD txartelean gorde</string> + <string name="notification_export_logs_title">Erregistroak SD txartelean gordetzen</string> <string name="choose_file">Fitxategia aukeratu</string> <string name="receiving_x_file">%1$s jasotzen (%2$d%% osatua)</string> <string name="download_x_file">%s deskargatu</string> @@ -350,6 +373,17 @@ <string name="reset">Berrezarri</string> <string name="account_image_description">Kontuaren profileko argazkia</string> <string name="copy_otr_clipboard_description">OTR hatz-marka arbelera kopiatu</string> + <string name="copy_omemo_clipboard_description">OMEMO hatz-marka arbelara kopiatu</string> + <string name="regenerate_omemo_key">OMEMO gakoa birsortu</string> + <string name="wipe_omemo_pep">Beste gailuak PEPetik garbitu</string> + <string name="clear_other_devices">Gailuak garbitu</string> + <string name="clear_other_devices_desc">Ziur al zaude OMEMO iragarpenetik beste gailu guztiak garbitu nahi dituzulaz? Zure gailuak konektatzen diren hurrengoan, beraiek berriragarriko dira, baina agian ez dute mezurik jasoko bitartean.</string> + <string name="purge_key">Gakoa purgatu</string> + <string name="purge_key_desc_part1">Ziur al zaude gako hau purgatu nahi duzulaz?</string> + <string name="purge_key_desc_part2">Modu itzulezinean arriskutsutzat hartuko da, eta ezingo duzu gakoarekin berriro saio berri bat sortu.</string> + <string name="error_no_keys_to_trust_server_error">Ez dago gako erabilgarririk kontaktu honetarako.\nEzin izan da zerbitzaritik gako berririk eskuratu. Agian akatsen bat dago zure kontaktuen zerbitzariarekin.</string> + <string name="error_no_keys_to_trust">Kontaktu honetarako gako erabilgarririk ez dago. Bere gakoak purgatu badituzu, berri batzuk sortu behar dituzte.</string> + <string name="error_trustkeys_title">Akatsa</string> <string name="fetching_history_from_server">Mezuak zerbitzaritik eskuratzen</string> <string name="no_more_history_on_server">Mezu gehiagorik ez zerbitzarian</string> <string name="updating">Eguneratzen...</string> @@ -364,7 +398,7 @@ <string name="manual_verification_explanation">Kontu handiz konpara ezazu beheko hatz-marka zure kontaktuaren hatz-markarekin.\nEnkriptatutako email bat edota telefono dei bat bezalako edozein komunikabide fidagarriren bat erabili dezakezu horiek trukatzeko.</string> <string name="change_password">Pasahitza aldatu</string> <string name="current_password">Oraingo pasahitza</string> - <string name="new_password">Pasahitza berria</string> + <string name="new_password">Pasahitz berria</string> <string name="password_should_not_be_empty">Pasahitza ez luke hutsik egon behar</string> <string name="enable_all_accounts">Kontu guztiak gaitu</string> <string name="disable_all_accounts">Kontu guztiak ezgaitu</string> @@ -387,8 +421,10 @@ <string name="public_conference">Publikoki edonor sar daiteken konferentzia</string> <string name="private_conference">Konferentzia pribatua, kideentzat soilik</string> <string name="conference_options">Konferentziaren aukerak</string> - <string name="members_only">Pribatua (kideak soilik)</string> + <string name="members_only">Pribatua, kideentzat soilik</string> <string name="non_anonymous">Ez anonimoa</string> + <string name="moderated">Moderatua</string> + <string name="you_are_not_participating">Ez zara parte hartzen ari</string> <string name="modified_conference_options">Konferentziaren aukerak aldatu dira</string> <string name="could_not_modify_conference_options">Konferentziaren aukerak ezin izan dira aldatu</string> <string name="never">Inoiz</string> @@ -397,7 +433,7 @@ <string name="two_hours">2 ordu</string> <string name="eight_hours">8 ordu</string> <string name="until_further_notice">abisatu arte</string> - <string name="pref_input_options">Sarrera aukerak</string> + <string name="pref_input_options">Sarrera</string> <string name="pref_enter_is_send">Sartu teklak bidaltzen du</string> <string name="pref_enter_is_send_summary">Sartu tekla erabili mezua bidaltzeko</string> <string name="pref_display_enter_key">Sartu tekla erakutsi</string> @@ -427,7 +463,6 @@ <string name="received_location">Kokapena jaso da</string> <string name="title_undo_swipe_out_conversation">Elkarrizketa itxi egin da</string> <string name="title_undo_swipe_out_muc">Konferentzia utzi egin da</string> - <string name="pref_certificate_options">Ziurtagirien aukerak</string> <string name="pref_dont_trust_system_cas_title">Sistemaren CAtaz ez fidatu</string> <string name="pref_dont_trust_system_cas_summary">Ziurtagiri guztiak eskuz onartu behar dira</string> <string name="pref_remove_trusted_certificates_title">Ziurtagiriak kendu</string> @@ -449,6 +484,75 @@ <string name="none">Bat ere ez</string> <string name="recently_used">Azkenengo aldiz erabilitakoa</string> <string name="choose_quick_action">Ekintza azkarra aukeratu</string> - <string name="file_not_found_on_remote_host">Fitxategia ez da aurkitu urruneko zerbitzarian</string> <string name="search_for_contacts_or_groups">Kontaktuak edo taldeak bilatu</string> + <string name="send_private_message">Mezu pribatua bidali</string> + <string name="user_has_left_conference">%s(e)k konferentzia utzi egin du</string> + <string name="username">Erabiltzaile izena</string> + <string name="username_hint">Erabiltzaile izena</string> + <string name="invalid_username">Hau ez da erabiltzaile izen baliodun bat</string> + <string name="download_failed_server_not_found">Deskargak huts egin du: zerbitzaria ez da aurkitu</string> + <string name="download_failed_file_not_found">Deskargak huts egin du: fitxategia ez da aurkitu</string> + <string name="download_failed_could_not_connect">Deskargak huts egin du: ezin izan da ostalarira konektatu</string> + <string name="account_status_tor_unavailable">Tor sarea ez dago eskuragarri</string> + <string name="server_info_broken">Hondatuta</string> + <string name="pref_presence_settings">Presentzia</string> + <string name="pref_away_when_screen_off">Urrun pantaila itzalita dagoenean</string> + <string name="pref_away_when_screen_off_summary">Zure baliabidea urrun bezala markatzen du pantaila itzalita dagoenean</string> + <string name="pref_xa_on_silent_mode">Ez eskuragarri modu isilean</string> + <string name="pref_xa_on_silent_mode_summary">Zure baliabidea ez eskuragarri bezala markatzen du gailua modu isilean dagoenean</string> + <string name="pref_show_connection_options">Konexioaren ezarpen luzatuak</string> + <string name="pref_show_connection_options_summary">Ostalariaren izena eta ataka ezarpenak erakutsi kontu bat ezartzerakoan</string> + <string name="hostname_example">xmpp.adibidea.com</string> + <string name="action_add_account_with_certificate">Kontua ziurtagiriarekin gehitu</string> + <string name="unable_to_parse_certificate">Ezin izan da ziurtagiria aztertu</string> + <string name="authenticate_with_certificate">Utzi hutsik ziurtagiririk gabe autentifikatzeko</string> + <string name="mam_prefs">Artxibatze hobespenak</string> + <string name="server_side_mam_prefs">Zerbitzariaren aldeko artxibatze hobespenak</string> + <string name="fetching_mam_prefs">Artxibatze hobespenak eskuratzen. Mesedez itxaron...</string> + <string name="unable_to_fetch_mam_prefs">Ezin izan dira artxibatze hobespenak eskuratu</string> + <string name="captcha_ocr">Captcharen testua</string> + <string name="captcha_required">Captcha beharrezkoa da</string> + <string name="captcha_hint">Sartu irudiaren testua</string> + <string name="certificate_chain_is_not_trusted">Ziurtagiriaren katea ez da fidagarria</string> + <string name="jid_does_not_match_certificate">Jabber IDa ez du ziurtagiriarekin bat egiten</string> + <string name="action_renew_certificate">Ziurtagiria berriztu</string> + <string name="error_fetching_omemo_key">Akatsa OMEMO gakoa eskuratzerakoan!</string> + <string name="verified_omemo_key_with_certificate">OMEMO gakoa ziurtagiriarekin egiaztatuta!</string> + <string name="device_does_not_support_certificates">Zure gailuak ez du bezero ziurtagiriak aukeratzea onartzen!</string> + <string name="pref_connection_options">Konexioa</string> + <string name="account_settings_hostname">Ostalariaren izena</string> + <string name="account_settings_port">Ataka</string> + <string name="not_a_valid_port">Hau ez da ataka zenbaki balioduna</string> + <string name="not_valid_hostname">Hau ez da ostalari izen balioduna</string> + <string name="connected_accounts">%2$dtik %1$d kontu konektatuta</string> + <plurals name="x_messages"> + <item quantity="one">mezu %d</item> + <item quantity="other">%d mezu</item> + </plurals> + <string name="shared_file_with_x">Fitxategia %s(r)ekin partekatu da</string> + <string name="shared_image_with_x">Irudia %s(r)ekin partekatu da</string> + <string name="no_storage_permission">Conversationsek kanpoko biltegirako sarbidea behar du</string> + <string name="sync_with_contacts">Kontaktuekin sinkronizatu</string> + <string name="sync_with_contacts_long">Conversationsek zure XMPP zerrenda eta zure kontaktuak uztartu nahi ditu haien izenak eta argazkiak erakusteko.\n\nConversationsek zure kontaktuak modu lokalean soilik irakurri eta uztartuko ditu, zure zerbitzarira kargatu gabe.\n\nJarraian baimenak eskatuko zaizkizu zure kontaktuetara sartu ahal izateko.</string> + <string name="certificate_information">Ziurtagiriaren informazioa</string> + <string name="certificate_subject">Subjektua</string> + <string name="certificate_issuer">Igorlea</string> + <string name="certificate_cn">Izen arrunta</string> + <string name="certificate_o">Erakundea</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(Ez eskuragarri)</string> + <string name="certificate_not_found">Ez da ziurtagiririk aurkitu</string> + <string name="notify_on_all_messages">Mezu guztiak jakinarazi</string> + <string name="notify_only_when_highlighted">Jakinarazi nabarmentzerakoan soilik</string> + <string name="notify_never">Jakinarazpenak ezgaituta</string> + <string name="notify_paused">Jakinarazpenak gelditu dira</string> + <string name="always">Beti</string> + <string name="automatically">Automatikoki</string> + <string name="battery_optimizations_enabled">Bateriaren optimizazioak gaituta</string> + <string name="battery_optimizations_enabled_explained">Zure gailua jakinarazpen atzeratuak edota mezuen galera ekar lezaketen bateriaren optimizazio handiak egiten ari da Conversationsen.\nHoriek ezgaitzea gomendatzen da.</string> + <string name="battery_optimizations_enabled_dialog">Zure gailua jakinarazpen atzeratuak edota mezuen galera ekar lezaketen bateriaren optimizazio handiak egiten ari da Conversationsen.\nJarraian hauek ezgaitzea eskatuko zaizu.</string> + <string name="disable">Ezgaitu</string> + <string name="selection_too_large">Hautatutako zatia handiegia da</string> + <string name="no_accounts">(Ez dago kontu aktiborik)</string> + <string name="this_field_is_required">Datu hau beharrezkoa da</string> </resources> diff --git a/src/main/res/values-ar-rSY/strings.xml b/src/main/res/values-fa-rIR/strings.xml index c757504a..c757504a 100644 --- a/src/main/res/values-ar-rSY/strings.xml +++ b/src/main/res/values-fa-rIR/strings.xml diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 0a42b56a..2a689797 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Conversation sécurisée</string> <string name="action_add_account">Ajouter un compte</string> <string name="action_edit_contact">Modifier le nom</string> - <string name="action_add_phone_book">Ajouter aux contacts</string> + <string name="action_add_phone_book">Ajouter au carnet d\'adresses</string> <string name="action_delete_contact">Retirer des contacts</string> <string name="action_block_contact">Bloquer le contact</string> <string name="action_unblock_contact">Débloquer le contact</string> @@ -20,28 +20,29 @@ <string name="title_activity_conference_details">Détails de la conférence</string> <string name="title_activity_contact_details">Détails du contact</string> <string name="title_activity_sharewith">Partager avec Conversation</string> - <string name="title_activity_start_conversation">Lancement de Conversation</string> + <string name="title_activity_start_conversation">Démarrer une conversation</string> <string name="title_activity_choose_contact">Choix du contact</string> <string name="title_activity_block_list">Bloquer la liste</string> <string name="just_now">À l\'instant</string> <string name="minute_ago">Il y a 1 minute</string> <string name="minutes_ago">Il y a %d minutes</string> <string name="unread_conversations">Conversations non lues</string> - <string name="sending">envoi…</string> - <string name="encrypted_message">Déchiffrement du message. Patientez…</string> + <string name="sending">Envoi…</string> + <string name="message_decrypting">Déchiffrement du message. Veuillez patienter...</string> + <string name="pgp_message">Message chiffré avec OpenPGP</string> <string name="nick_in_use">Cet identifiant est déjà utilisé.</string> <string name="admin">Administrateur</string> <string name="owner">Propriétaire</string> <string name="moderator">Modérateur</string> <string name="participant">Participant</string> <string name="visitor">Visiteur</string> - <string name="remove_contact_text">Voulez-vous supprimer %s de votre liste? Les conversations associées à ce compte ne seront pas supprimées.</string> - <string name="block_contact_text">Voulez-vous bloquer %s de vous envoyer des messages?</string> - <string name="unblock_contact_text">Voulez-vous débloquer %s et leur permettre de vous envoyer des messages?</string> - <string name="block_domain_text">Bloquer tous les contacts de %s?</string> - <string name="unblock_domain_text">Débloquer tous les contacts de %s?</string> + <string name="remove_contact_text">Voulez-vous supprimer %s de votre liste ? Les conversations associées à ce contact ne seront pas supprimées.</string> + <string name="block_contact_text">Voulez-vous bloquer %s pour l\'empêcher de vous envoyer des messages ?</string> + <string name="unblock_contact_text">Voulez-vous débloquer %s et lui permettre de vous envoyer des messages?</string> + <string name="block_domain_text">Bloquer tous les contacts de %s ?</string> + <string name="unblock_domain_text">Débloquer tous les contacts de %s ?</string> <string name="contact_blocked">Contact bloqué</string> - <string name="remove_bookmark_text">Voulez-vous retirer %s des favoris? La conversation associée avec ce favoris ne sera pas supprimé.</string> + <string name="remove_bookmark_text">Voulez-vous retirer %s des favoris? La conversation associée à ce favori ne sera pas supprimée.</string> <string name="register_account">Créer un nouveau compte sur le serveur</string> <string name="change_password_on_server">Changer de mot de passe sur le serveur</string> <string name="share_with">Partager avec…</string> @@ -57,120 +58,122 @@ <string name="unblock">Débloquer</string> <string name="save">Enregistrer</string> <string name="ok">OK</string> - <string name="crash_report_title">Conversations s\'est arreté</string> - <string name="crash_report_message">En envoyant des logs vous aidez au développement de Conversations.\n\n<b>Attention:</b> Votre compte XMPP sera utilisé pour envoyer les logs aux développeurs.</string> + <string name="crash_report_title">Conversations a planté</string> + <string name="crash_report_message">En envoyant des logs, vous aidez le développement de Conversations.\n\n<b>Attention :</b> Votre compte XMPP sera utilisé pour envoyer les logs aux développeurs.</string> <string name="send_now">Envoyer</string> <string name="send_never">Ne plus me demander</string> <string name="problem_connecting_to_account">Impossible de se connecter au compte.</string> <string name="problem_connecting_to_accounts">Impossible de se connecter aux comptes.</string> <string name="touch_to_fix">Appuyez pour gérer vos comptes.</string> - <string name="attach_file">Lier un fichier</string> - <string name="not_in_roster">Le contact n\'est pas dans votre carnet d\'adresses. Voulez-vous l\'y ajouter?</string> + <string name="attach_file">Joindre un fichier</string> + <string name="not_in_roster">Le contact n\'est pas dans votre carnet d\'adresses. Voulez-vous l\'y ajouter ?</string> <string name="add_contact">Ajouter un contact</string> - <string name="send_failed">Echec de l\'envoi.</string> + <string name="send_failed">Échec de l\'envoi.</string> <string name="send_rejected">Rejeté</string> - <string name="preparing_image">Préparation de la transmission de l\'image. Patientez…</string> + <string name="preparing_image">Préparation de l\'image pour envoi...</string> <string name="action_clear_history">Vider l\'historique</string> <string name="clear_conversation_history">Vider l\'historique de la conversation</string> - <string name="clear_histor_msg">Voulez-vous supprimer tous les messages de cette conversation?\n\n<b>Attention:</b> Les messages seront supprimés uniquement sur ce périphérique.</string> + <string name="clear_histor_msg">Voulez-vous supprimer tous les messages de cette conversation ?\n\n<b>Attention :</b> Les messages seront supprimés uniquement sur cet appareil.</string> <string name="delete_messages">Supprimer les messages</string> - <string name="also_end_conversation">Terminer plus tard cette conversation</string> + <string name="also_end_conversation">Fermer cette conversation ensuite</string> <string name="choose_presence">Choisir le status de présence</string> - <string name="send_plain_text_message">Envoyer un message</string> - <string name="send_otr_message">Envoyer un message sécurisé par OTR</string> - <string name="send_pgp_message">Envoyer un message sécurisé par OpenPGP</string> + <string name="send_unencrypted_message">Envoyer un message non chiffré</string> + <string name="send_otr_message">Envoyer un message chiffré avec OTR</string> + <string name="send_omemo_message">Envoyé un message chiffré avec OMEMO</string> + <string name="send_omemo_x509_message">Envoyé un message chiffré avec \\OMEMO</string> + <string name="send_pgp_message">Envoyer un message chiffré avec OpenPGP</string> <string name="your_nick_has_been_changed">Votre identifiant a été changé</string> <string name="send_unencrypted">Envoyer en clair</string> - <string name="decryption_failed">Echec du déchiffrement. Merci de vérifier la clef privée utilisée.</string> + <string name="decryption_failed">Echec du déchiffrement. Avez-vous la bonne clef privée ?</string> <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Conversations requiert une application tierce nommée <b>OpenKeychain</b> pour chiffrer et déchiffrer les messages.\n\nOpenKeychain est sous licence GPLv3 et est disponible sur F-Droid et Google Play.\n\n<small>(Merci de redémarrer Conversations apres l\'installation du logiciel)</small></string> + <string name="openkeychain_required_long">Conversations requiert une application tierce nommée <b>OpenKeychain</b> pour chiffrer et déchiffrer les messages.\n\nOpenKeychain est sous licence GPLv3 et est disponible sur F-Droid et Google Play.\n\n<small>(Veuillez redémarrer Conversations apres l\'installation de l\'app)</small></string> <string name="restart">Redémarrer</string> <string name="install">Installer</string> + <string name="openkeychain_not_installed">Veuillez installer OpenKeychain</string> <string name="offering">Proposition…</string> <string name="waiting">Patientez…</string> <string name="no_pgp_key">Aucune clef OpenPGP trouvée.</string> - <string name="contact_has_no_pgp_key">Conversations ne peut chiffrer vos messages car votre correspondant n\'a pas communiqué sa clef publique.\n\n<small>Merci de demander à votre correspondant de configurer OpenPGP.</small></string> - <string name="no_pgp_keys">Aucune clef OpenPGP n\'est disponible.</string> - <string name="contacts_have_no_pgp_keys">Conversations ne peut pas chiffrer votre message car vous ne connaissez pas la clef publique de vos contacts.\n\n<small>Merci de les faire configurer leur OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Message chiffré reçu. Appuyez pour le déchiffrer.</i></string> + <string name="contact_has_no_pgp_key">Conversations ne peut pas chiffrer vos messages car votre contact n\'a pas communiqué sa clef publique.\n\n<small>Demandez-lui de configurer OpenPGP.</small></string> + <string name="no_pgp_keys">Aucune clef OpenPGP n\'a été trouvée.</string> + <string name="contacts_have_no_pgp_keys">Conversations ne peut pas chiffrer votre message car vos contacts ne communiquent pas leur clef publique.\n\n<small>Demandez-leur de configurer OpenPGP.</small></string> + <string name="encrypted_message_received"><i>Message chiffré reçu. Appuyez pour déchiffrer.</i></string> <string name="pref_general">Général</string> <string name="pref_xmpp_resource">Ressource XMPP</string> - <string name="pref_xmpp_resource_summary">Nom permettant d\'identifier ce client XMPP</string> + <string name="pref_xmpp_resource_summary">Nom utilisé par ce client pour s\'identifier</string> <string name="pref_accept_files">Accepter les fichiers</string> - <string name="pref_accept_files_size_summary">Accepter automatiquement les fichiers plus petits que…</string> - <string name="pref_notification_settings">Paramètres de notification</string> + <string name="pref_accept_files_summary">Accepter automatiquement les fichiers plus petits que…</string> + <string name="pref_notification_settings">Notification</string> <string name="pref_notifications">Notifications</string> - <string name="pref_notifications_summary">Notifier l\'arrivée d\'un message</string> + <string name="pref_notifications_summary">Notifier de l\'arrivée d\'un message.</string> <string name="pref_vibrate">Vibration</string> - <string name="pref_vibrate_summary">Vibrer lors de l\'arrivée d\'un message</string> + <string name="pref_vibrate_summary">Vibrer lors de l\'arrivée d\'un message.</string> <string name="pref_sound">Sonore</string> - <string name="pref_sound_summary">Jouer une sonnerie lors de l\'arrivée d\'un message</string> - <string name="pref_conference_notifications">Notifications lors des conférences</string> - <string name="pref_conference_notifications_summary">Toujours notifier l\'arrivée d\'un message provenant d\'une conférence.</string> + <string name="pref_sound_summary">Jouer une sonnerie pour notifier.</string> <string name="pref_notification_grace_period">Période sans notification</string> <string name="pref_notification_grace_period_summary">Désactiver momentanément les notifications après l\'arrivée d\'une copie carbone.</string> - <string name="pref_advanced_options">Options avancées</string> - <string name="pref_never_send_crash">Ne jamais envoyer de rapports d\'erreurs</string> - <string name="pref_never_send_crash_summary">En envoyant des logs vous aidez au développement de Conversations.</string> + <string name="pref_advanced_options">Avancé</string> + <string name="pref_never_send_crash">Ne pas envoyer de rapports d\'erreurs</string> + <string name="pref_never_send_crash_summary">En envoyant des logs vous aidez le développement de Conversations.</string> <string name="pref_confirm_messages">Confirmation de lecture</string> - <string name="pref_confirm_messages_summary">Informer l\'expéditeur d\'un message de sa bonne réception.</string> - <string name="pref_ui_options">Options d\'affichage</string> - <string name="openpgp_error">Une erreur s\'est produite via OpenKeychain</string> + <string name="pref_confirm_messages_summary">Informer le contact lorsque vous avez reçu et lu un message.</string> + <string name="pref_ui_options">Interface</string> + <string name="openpgp_error">OpenKeychain a signalé une erreur</string> <string name="error_decrypting_file">Erreur d\'E/S lors du déchiffrement du fichier</string> <string name="accept">Accepter</string> <string name="error">Une erreur s\'est produite</string> - <string name="pref_grant_presence_updates">Accepter les mises à jour de présence</string> - <string name="pref_grant_presence_updates_summary">Demander et accepter par avance les mises à jour de présence des contacts créés.</string> + <string name="pref_grant_presence_updates">Autoriser les màj de présence</string> + <string name="pref_grant_presence_updates_summary">Autoriser et demander par avance les mises à jour de présence des contacts ajoutés.</string> <string name="subscriptions">Publications</string> <string name="your_account">Votre compte</string> <string name="keys">Clefs</string> - <string name="send_presence_updates">Envoyer les mises à jour de présence</string> - <string name="receive_presence_updates">Recevoir les mises à jour de présence</string> - <string name="ask_for_presence_updates">Demander les mises à jour de présence</string> + <string name="send_presence_updates">Envoyer mes màj de présence</string> + <string name="receive_presence_updates">Recevoir ses màj de présence</string> + <string name="ask_for_presence_updates">Demander les màj de présence</string> <string name="attach_choose_picture">Choisir une image</string> <string name="attach_take_picture">Prendre une photo</string> <string name="preemptively_grant">Accepter par avance les demandes de publication.</string> <string name="error_not_an_image_file">Le fichier choisi n\'est pas une image</string> <string name="error_compressing_image">Une erreur s\'est produite en convertissant l\'image</string> - <string name="error_file_not_found">Fichier non trouvé</string> - <string name="error_io_exception">Erreur générale d\'E/S. Avez-vous encore de l\'espace libre?</string> - <string name="error_security_exception_during_image_copy">L\'application utilisée empêche la lecture de l\'image.\n\n<small>Choisissez l\'image depuis une autre application.</small></string> + <string name="error_file_not_found">Impossible de trouver le fichier</string> + <string name="error_io_exception">Erreur générale d\'E/S. Avez-vous encore de l\'espace libre ?</string> + <string name="error_security_exception_during_image_copy">L\'application utilisée ne nous donne pas la permission de lire l\'image.\n\n<small>Utilisez une autre application pour choisir une image.</small></string> <string name="account_status_unknown">Inconnu</string> <string name="account_status_disabled">Désactivé temporairement</string> <string name="account_status_online">En ligne</string> <string name="account_status_connecting">Connexion\u2026</string> <string name="account_status_offline">Hors-ligne</string> <string name="account_status_unauthorized">Non autorisé</string> - <string name="account_status_not_found">Serveur non trouvé</string> + <string name="account_status_not_found">Impossible de trouver le serveur</string> <string name="account_status_no_internet">Aucune connectivité</string> - <string name="account_status_regis_fail">Enregistrement échoué</string> + <string name="account_status_regis_fail">Échec de l\'enregistrement</string> <string name="account_status_regis_conflict">Identifiant déjà utilisé</string> <string name="account_status_regis_success">Enregistrement réussi</string> <string name="account_status_regis_not_sup">Le serveur ne permet pas l\'enregistrement</string> <string name="account_status_security_error">Erreur de sécurité</string> <string name="account_status_incompatible_server">Serveur incompatible</string> - <string name="encryption_choice_none">Texte clair</string> + <string name="encryption_choice_unencrypted">Non chiffré</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Modifier le compte</string> <string name="mgmt_account_delete">Supprimer</string> <string name="mgmt_account_disable">Désactiver temporairement</string> <string name="mgmt_account_publish_avatar">Publier un avatar</string> <string name="mgmt_account_publish_pgp">Publier la clef publique OpenPGP</string> <string name="mgmt_account_enable">Activer</string> - <string name="mgmt_account_are_you_sure">Êtes-vous sûr?</string> - <string name="mgmt_account_delete_confirm_text">En supprimant votre compte, votre historique de conversations sera perdu!</string> + <string name="mgmt_account_are_you_sure">Êtes-vous sûr ?</string> + <string name="mgmt_account_delete_confirm_text">Si vous supprimez votre compte, votre historique de conversations sera perdu !</string> <string name="attach_record_voice">Enregistrer un son</string> <string name="account_settings_jabber_id">Identifiant</string> <string name="account_settings_password">Mot de passe</string> - <string name="account_settings_example_jabber_id">utilisateur@exemple.com</string> + <string name="account_settings_example_jabber_id">nom@exemple.com</string> <string name="account_settings_confirm_password">Confirmer le mot de passe</string> <string name="password">Mot de passe</string> <string name="confirm_password">Confirmer le mot de passe</string> - <string name="passwords_do_not_match">Les deux mots de passes ne correspondent pas.</string> - <string name="invalid_jid">Ce n\'est pas un identifiant valide.</string> + <string name="passwords_do_not_match">Les deux mots de passe ne correspondent pas.</string> + <string name="invalid_jid">Cet identifiant n\'est pas valide.</string> <string name="error_out_of_memory">Plus de mémoire disponible. L\'image est trop volumineuse.</string> - <string name="add_phone_book_text">Voulez-vous ajouter %s aux contacts du téléphone?</string> + <string name="add_phone_book_text">Voulez-vous ajouter %s à votre carnet d\'adresses ?</string> <string name="contact_status_online">En ligne</string> <string name="contact_status_free_to_chat">Disponible</string> <string name="contact_status_away">Absent</string> @@ -180,16 +183,18 @@ <string name="muc_details_conference">Conférence</string> <string name="muc_details_other_members">Autres membres</string> <string name="server_info_show_more">Infos sur le serveur</string> - <string name="server_info_mam">XEP-0313: MAM</string> - <string name="server_info_carbon_messages">Copies carbone</string> - <string name="server_info_csi">XEP-0352: Client State Indication</string> - <string name="server_info_blocking">XEP-0191: Blocking Command</string> - <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> - <string name="server_info_stream_management">Gestion des flux</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> - <string name="server_info_available">disponible</string> - <string name="server_info_unavailable">indisponible</string> - <string name="missing_public_keys">Aucune annonce de clef publique</string> + <string name="server_info_mam">XEP-0313 : MAM</string> + <string name="server_info_carbon_messages">XEP-0280 : Copies carbone</string> + <string name="server_info_csi">XEP-0352 : Indication status client</string> + <string name="server_info_blocking">XEP-0191 : Commande de bloquage</string> + <string name="server_info_roster_version">XEP-0237 : Versionnement contacts</string> + <string name="server_info_stream_management">XEP-0198 : Gestion des flux</string> + <string name="server_info_pep">XEP-0163 : PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363 : Envoi de fichiers via HTTP</string> + <string name="server_info_push">XEP-0357 : Notifications Push</string> + <string name="server_info_available">supporté</string> + <string name="server_info_unavailable">non supporté</string> + <string name="missing_public_keys">Annonce de clef publique manquante</string> <string name="last_seen_now">en ligne à l\'instant</string> <string name="last_seen_min">en ligne il y a 1 minute</string> <string name="last_seen_mins">en ligne il y a %d minutes</string> @@ -198,170 +203,203 @@ <string name="last_seen_day">en ligne hier</string> <string name="last_seen_days">en ligne il y a %d jours</string> <string name="never_seen">jamais vu en ligne</string> - <string name="install_openkeychain">Message chiffré. Merci d\'installer OpenKeychain pour lire le contenu du message.</string> + <string name="install_openkeychain">Message chiffré. Veuillez installer OpenKeychain pour le déchiffrer.</string> <string name="unknown_otr_fingerprint">Empreinte OTR inconnue.</string> - <string name="openpgp_messages_found">Messages chiffrés par OpenPGP détectés.</string> - <string name="reception_failed">Echec lors de la réception</string> + <string name="openpgp_messages_found">Messages chiffrés avec OpenPGP détectés.</string> + <string name="reception_failed">Échec lors de la réception</string> <string name="your_fingerprint">Votre empreinte</string> <string name="otr_fingerprint">Empreinte OTR</string> + <string name="omemo_fingerprint">Empreinte OMEMO</string> + <string name="omemo_fingerprint_x509">v\\Empreinte OMEMO</string> + <string name="omemo_fingerprint_selected_message">Empreinte OMEMO du message</string> + <string name="omemo_fingerprint_x509_selected_message">v\\Empreinte OMEMO du message</string> + <string name="this_device_omemo_fingerprint">Votre empreinte OMEMO</string> + <string name="other_devices">Autres appareils</string> + <string name="trust_omemo_fingerprints">Faire confiance aux empreintes OMEMO</string> + <string name="fetching_keys">Récupération des clefs...</string> + <string name="done">Terminé</string> <string name="verify">Vérifier</string> <string name="decrypt">Déchiffrer</string> <string name="conferences">Conférences</string> <string name="search">Rechercher</string> <string name="create_contact">Ajouter un contact</string> + <string name="enter_contact">Ajouter contact</string> <string name="join_conference">Rejoindre la conférence</string> <string name="delete_contact">Supprimer le contact</string> <string name="view_contact_details">Afficher les détails du contact</string> <string name="block_contact">Bloquer le contact</string> <string name="unblock_contact">Débloquer le contact</string> <string name="create">Ajouter</string> + <string name="select">Sélectionner</string> <string name="contact_already_exists">Le contact existe déjà.</string> <string name="join">Rejoindre</string> <string name="conference_address">Adresse de la conférence</string> <string name="conference_address_example">salle@conference.exemple.com</string> - <string name="save_as_bookmark">Enregistrer en favoris</string> - <string name="delete_bookmark">Supprimer le favoris</string> - <string name="bookmark_already_exists">Ce favoris existe déjà.</string> + <string name="save_as_bookmark">Enregistrer comme favori</string> + <string name="delete_bookmark">Supprimer le favori</string> + <string name="bookmark_already_exists">Le favori existe déjà</string> <string name="you">Vous</string> <string name="action_edit_subject">Modifier le sujet de la conférence</string> - <string name="conference_not_found">Conférence non trouvée</string> + <string name="conference_not_found">Impossible de trouver la conférence</string> + <string name="conference_unknown_error">Erreur inconnue reçue</string> <string name="leave">Partir</string> <string name="contact_added_you">Votre correspondant vous a ajouté dans sa liste de contacts</string> - <string name="add_back">Ajouter également</string> - <string name="contact_has_read_up_to_this_point">%s a lu les messages précédents.</string> + <string name="add_back">Ré-ajouter</string> + <string name="contact_has_read_up_to_this_point">%s a tout lu jusqu\'à ici</string> <string name="publish">Publier</string> <string name="touch_to_choose_picture">Toucher l\'avatar pour choisir une image depuis la galerie.</string> - <string name="publish_avatar_explanation">Nota Bene: Les personnes ayant activé les mises jour de présence verront cette image.</string> + <string name="publish_avatar_explanation">Note : toutes les personnes ayant souscrit à vos mises jour de présence verront cette image.</string> <string name="publishing">Mise à jour…</string> - <string name="error_publish_avatar_server_reject">Le serveur a rejeté votre envoi d\'image</string> + <string name="error_publish_avatar_server_reject">Le serveur a rejeté votre publication</string> <string name="error_publish_avatar_converting">Une erreur s\'est produite pendant la conversion de votre image.</string> - <string name="error_saving_avatar">Impossible de stocker l\'image sur le disque</string> - <string name="or_long_press_for_default">(Un appui long réinitialise le paramètre par defaut)</string> - <string name="error_publish_avatar_no_server_support">Votre serveur n\'autorise pas l\'envoi d\'avatars</string> + <string name="error_saving_avatar">Impossible de stocker l\'avatar sur le disque</string> + <string name="or_long_press_for_default">(Un appui long réinitialise le paramètre)</string> + <string name="error_publish_avatar_no_server_support">Votre serveur n\'autorise pas la publication d\'avatars</string> <string name="private_message">chuchoté</string> <string name="private_message_to">pour %s</string> <string name="send_private_message_to">Envoyer un message privé à %s</string> <string name="connect">Se connecter</string> <string name="account_already_exists">Ce compte existe déjà</string> - <string name="next">suivant</string> + <string name="next">Suivant</string> <string name="server_info_session_established">Session établie</string> <string name="additional_information">Informations supplémentaires</string> <string name="skip">Passer</string> <string name="disable_notifications">Désactiver les notifications</string> <string name="disable_notifications_for_this_conversation">Désactiver les notifications pour cette conversation</string> - <string name="notifications_disabled">Notifications are Désactivées</string> <string name="enable">Activer</string> - <string name="conference_requires_password">La conférence necessite un mot de passe</string> + <string name="conference_requires_password">La conférence nécessite un mot de passe</string> <string name="enter_password">Entrer le mot de passe</string> - <string name="missing_presence_updates">Mise à jour de présence non connue</string> - <string name="request_presence_updates">Merci de demander à votre contact de fournir les mises à jour de présence.\n\n<small>Cela permettra de savoir quel matériel utilise votre contact.</small></string> + <string name="missing_presence_updates">Mises à jour de présence manquantes pour ce contact</string> + <string name="request_presence_updates">Veuillez demander à votre contact de partager ses mises à jour de présence.\n\n<small>Elles seront utilisées pour déterminer son client.</small></string> <string name="request_now">Demander maintenant</string> <string name="delete_fingerprint">Supprimer l\'empreinte</string> - <string name="sure_delete_fingerprint">Etes-vous sûr de vouloir supprimer l\'empreinte?</string> + <string name="sure_delete_fingerprint">Etes-vous sûr de vouloir supprimer l\'empreinte ?</string> <string name="ignore">Ignorer</string> - <string name="without_mutual_presence_updates"><b>Attention:</b> Ceci peut poser problème si l\'un des deux correspondants n\'a pas activé les mises à jour de présence.\n\n<small>Go to contact details to verify your presence subscriptions.</small></string> - <string name="pref_encryption_settings">Paramètres de chiffrement</string> + <string name="without_mutual_presence_updates"><b>Attention :</b> peut poser problème si l\'un des deux correspondants n\'a pas activé les mises à jour de présence.\n\n<small>Vérifiez dans les détails du contact que vous y avez bien souscrit.</small></string> + <string name="pref_security_settings">Sécurité</string> <string name="pref_force_encryption">Forcer le chiffrement de bout en bout</string> <string name="pref_force_encryption_summary">Toujours envoyer des messages chiffrés (sauf pour les conférences)</string> - <string name="pref_dont_save_encrypted">Ne pas sauvegarder les messages chiffrés</string> - <string name="pref_dont_save_encrypted_summary">Attention: Celà peut mener à une perte de messages</string> - <string name="pref_expert_options">Options avancées</string> - <string name="pref_expert_options_summary">A utiliser avec précautions</string> - <string name="title_activity_about">Sur Conversations</string> - <string name="pref_about_conversations_summary">Informations sur le build et les licenses</string> + <string name="pref_allow_message_correction">Autoriser la correction</string> + <string name="pref_allow_message_correction_summary">Permet à vos contacts d\'éditer leurs messages rétroactivement</string> + <string name="pref_dont_save_encrypted">Messages chiffrés non sauvegardés</string> + <string name="pref_dont_save_encrypted_summary">Attention : peut provoquer la perte de messages.</string> + <string name="pref_expert_options">Paramètres avancés</string> + <string name="pref_expert_options_summary">À utiliser avec précaution.</string> + <string name="title_activity_about">À propos</string> + <string name="pref_about_conversations_summary">Informations sur la version et les licenses</string> <string name="title_pref_quiet_hours">Heures tranquilles</string> <string name="title_pref_quiet_hours_start_time">Heure de début</string> <string name="title_pref_quiet_hours_end_time">Heure de fin</string> <string name="title_pref_enable_quiet_hours">Activer les heures tranquilles</string> - <string name="pref_quiet_hours_summary">Les notifications seront rendu muets pendant les heures tranquilles</string> + <string name="pref_quiet_hours_summary">Les notifications seront muettes pendant les heures tranquilles.</string> <string name="pref_use_larger_font">Augmenter la taille du texte</string> - <string name="pref_use_larger_font_summary">Augmenter la taille du texte partout dans l\'application</string> - <string name="pref_use_send_button_to_indicate_status">Le bouton Envoyer permet d\'indiquer le statut</string> - <string name="pref_use_indicate_received">Accusé de reception</string> - <string name="pref_use_indicate_received_summary">Les messages recus seront marqués d\'une coche verte si disponible</string> - <string name="pref_use_send_button_to_indicate_status_summary">Adapter la couleur du bouton Envoyer pour indiquer le statut</string> + <string name="pref_use_larger_font_summary">Augmenter la taille du texte partout dans l\'application.</string> + <string name="pref_use_send_button_to_indicate_status">Statut sur le bouton Envoyer</string> + <string name="pref_use_indicate_received">Accusés de réception</string> + <string name="pref_use_indicate_received_summary">Les messages reçus seront marqués d\'une coche verte (si supporté).</string> + <string name="pref_use_send_button_to_indicate_status_summary">Le bouton Envoyer change de couleur pour indiquer le statut du contact.</string> <string name="pref_expert_options_other">Autres</string> <string name="pref_conference_name">Nom de la conférence </string> - <string name="pref_conference_name_summary">Identifier la conférence par son nom plutot que par son JID</string> - <string name="toast_message_otr_fingerprint">Empreinte OTR copiée dans le presse-papier!</string> - <string name="conference_banned">Vous êtes interdit de cette conférence</string> + <string name="pref_conference_name_summary">Identifier les conférences par leur sujet plutôt que leur JID.</string> + <string name="pref_autojoin">Rejoindre automatiquement les conférences</string> + <string name="pref_autojoin_summary">Respecter le paramètre de connexion automatique des favoris de conférence</string> + <string name="toast_message_otr_fingerprint">Empreinte OTR copiée dans le presse-papier !</string> + <string name="toast_message_omemo_fingerprint">Empreinte OMEMO copiée dans le presse-papier !</string> + <string name="conference_banned">Vous êtes banni de cette conférence</string> <string name="conference_members_only">Cette conférence est réservée aux membres</string> <string name="conference_kicked">Vous avez été éjecté de cette conférence</string> - <string name="using_account">utiliser le compte %s</string> - <string name="not_connected_try_again">Vous n\'êtes pas connecté. Merci de retenter plus tard.</string> + <string name="using_account">avec le compte %s</string> + <string name="checking_x">Vérification de %s sur l\'hôte HTTP</string> + <string name="not_connected_try_again">Vous n\'êtes pas connecté. Essayez plus tard.</string> + <string name="check_x_filesize">Vérification de la taille de %s</string> <string name="message_options">Options du message</string> <string name="copy_text">Copier le texte</string> <string name="copy_original_url">Copier l\'URL</string> <string name="send_again">Envoyer de nouveau</string> + <string name="file_url">URL du fichier</string> <string name="message_text">Message texte</string> <string name="url_copied_to_clipboard">URL copiée dans le presse-papier</string> <string name="message_copied_to_clipboard">Message copié dans le presse-papier</string> - <string name="image_transmission_failed">Echec lors de l\'envoi de l\'image</string> + <string name="image_transmission_failed">Échec lors de l\'envoi de l\'image</string> <string name="scan_qr_code">Scanner un QR code</string> - <string name="show_qr_code">Afficher le QR code</string> + <string name="show_qr_code">Afficher le code QR</string> <string name="show_block_list">Afficher la liste des contacts bloqués</string> <string name="account_details">Détails du compte</string> <string name="verify_otr">Vérifier l\'OTR</string> <string name="remote_fingerprint">Supprimer l\'empreinte</string> <string name="scan">Scanner</string> - <string name="or_touch_phones">(ou les touches)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Indice ou question</string> <string name="shared_secret_secret">Secret partagé</string> <string name="confirm">Confirmer</string> <string name="in_progress">En cours</string> <string name="respond">Répondre</string> - <string name="failed">Echoué</string> + <string name="failed">Échec</string> <string name="secrets_do_not_match">Les secrets ne correspondent pas</string> <string name="try_again">Réessayer</string> <string name="finish">Terminé</string> - <string name="verified">Vérifié!</string> + <string name="verified">Vérifié !</string> <string name="smp_requested">Le contact requiert la vérification du SMP</string> - <string name="no_otr_session_found">Aucune session valide d\'OTR n\'a été trouvée!</string> + <string name="no_otr_session_found">Aucune session OTR valide n\'a été trouvée !</string> <string name="conversations_foreground_service">Conversations</string> - <string name="pref_keep_foreground_service">Garder le service au premier-plan</string> - <string name="pref_keep_foreground_service_summary">Évite que le système ferme votre connexion</string> - <string name="choose_file">Choix d\'un fichier</string> + <string name="pref_keep_foreground_service">Garder le service au 1er plan</string> + <string name="pref_keep_foreground_service_summary">Évite que le système ne ferme votre connexion.</string> + <string name="pref_export_logs">Exporter les historiques</string> + <string name="pref_export_logs_summary">Sauvegarder les historiques sur la carte SD</string> + <string name="notification_export_logs_title">Sauvegarde des historiques sur la carte SD...</string> + <string name="choose_file">Choix du fichier</string> <string name="receiving_x_file">Réception %1$s (%2$d%% complété)</string> - <string name="download_x_file">Télecharger %s</string> + <string name="download_x_file">Télécharger %s</string> <string name="file">fichier</string> <string name="open_x_file">Ouvrir %s</string> <string name="sending_file">envoi (%1$d%% complété)</string> <string name="preparing_file">Préparation du fichier pour l\'envoi</string> - <string name="x_file_offered_for_download">%s disponible pour téléchargement</string> + <string name="x_file_offered_for_download">%s proposé à télécharger</string> <string name="cancel_transmission">Annuler l\'envoi</string> - <string name="file_transmission_failed">Envoi du fichier échoué</string> + <string name="file_transmission_failed">Échec de l\'envoi du fichier</string> <string name="file_deleted">Le fichier a été supprimé</string> <string name="no_application_found_to_open_file">Aucune application disponible pour ouvrir le fichier</string> <string name="could_not_verify_fingerprint">Impossible de vérifier l\'empreinte</string> <string name="manually_verify">Vérifier manuellement</string> - <string name="are_you_sure_verify_fingerprint">Etes-vous sûr de vouloir vérifier l\'empreinte OTR de vos contacts?</string> + <string name="are_you_sure_verify_fingerprint">Êtes-vous sûr de vouloir vérifier l\'empreinte OTR de vos contacts ?</string> <string name="pref_show_dynamic_tags">Afficher les tags dynamiques</string> - <string name="pref_show_dynamic_tags_summary">Afficher les tags en lecture-seule sous les contacts</string> + <string name="pref_show_dynamic_tags_summary">Afficher des tags en lecture seule en dessous des contacts.</string> <string name="enable_notifications">Activer les notifications</string> <string name="conference_with">Créer une conférence avec…</string> <string name="no_conference_server_found">Aucun serveur de conférence disponible</string> - <string name="conference_creation_failed">Echec de la création de la conférence!</string> - <string name="conference_created">Conférence créée!</string> - <string name="secret_accepted">Secret commun accepté!</string> + <string name="conference_creation_failed">Échec de la création de la conférence !</string> + <string name="conference_created">Conférence créée !</string> + <string name="secret_accepted">Secret accepté !</string> <string name="reset">Réinitialiser</string> - <string name="account_image_description">Image du compte</string> + <string name="account_image_description">Avatar du compte</string> <string name="copy_otr_clipboard_description">Copier l\'empreinte OTR dans le presse-papier</string> - <string name="fetching_history_from_server">Récupérer l\'historique depuis le serveur</string> - <string name="no_more_history_on_server">Fin de l\'historique sur le serveur</string> + <string name="copy_omemo_clipboard_description">Copier l\'empreinte OMEMO dans le presse-papier</string> + <string name="regenerate_omemo_key">Régénérer l\'empreinte OMEMO</string> + <string name="wipe_omemo_pep">Effacer les autres appareils de PEP</string> + <string name="clear_other_devices">Supprimer les appareils</string> + <string name="clear_other_devices_desc">Êtes-vous sûr de vouloir supprimer les autres appareils de l\'annonce OMEMO ? Ils s\'annonceront de nouveau à leur prochaine connexion, mais ils peuvent ne pas recevoir les messages envoyés entre temps.</string> + <string name="purge_key">Supprimer la clef</string> + <string name="purge_key_desc_part1">Êtes-vous sûr de vouloir supprimer cette clef ?</string> + <string name="purge_key_desc_part2">Elle sera considérée compromise de manière irréversible, et vous ne pourrez plus générer de session avec.</string> + <string name="error_no_keys_to_trust_server_error">Il n\'y a aucune clef utilisable disponible pour ce contact.\nLa récupération de nouvelles clefs sur le serveur a échoué. Peut-être y a-t-il un problème avec votre serveur de contacts ?</string> + <string name="error_no_keys_to_trust">il n\'y a pas de clef disponible pour ce contact. Si vous avez purgé toutes ses clefs, il doit en générer de nouvelles.</string> + <string name="error_trustkeys_title">Erreur</string> + <string name="fetching_history_from_server">Récupération de l\'historique sur le serveur</string> + <string name="no_more_history_on_server">Plus d\'historique sur le serveur</string> <string name="updating">Mise à jour…</string> - <string name="password_changed">Mot de passe modifié!</string> + <string name="password_changed">Mot de passe modifié !</string> <string name="could_not_change_password">Impossible de changer le mot de passe</string> <string name="otr_session_not_started">Envoyez un message pour commencer la conversation chiffrée</string> <string name="ask_question">Poser une question</string> - <string name="smp_explain_answer">Votre contact voudrait vous identifier de manière sûre grâce à un secret commun. Il vous envoie le message ou la question suivante.</string> - <string name="shared_secret_hint_should_not_be_empty">Votre indice ne devrait pas être vide</string> - <string name="shared_secret_can_not_be_empty">Votre secret ne peut être vide</string> - <string name="manual_verification_explanation">Comparez avec soin l\'empreinte de votre contact avec celle indiquée ci-dessous.\nVous pouvez utiliser n\'importe quel moyen de communication sécurisée pour cela, tel que le télephone ou l\'envoi d\'un e-mail chiffré.</string> + <string name="smp_explain_question">Si vous et votre contact avez en commun un secret que personne d\'autre ne connait (comme une blague ou ce que vous avez mangé lors de votre dernière rencontre), vous pouvez utiliser ce secret pour vérifier vos empreintes respectives.\n\nVous donnez un indice ou posez votre question à votre contact, qui répondra en faisant attention à la casse.</string> + <string name="smp_explain_answer">Votre contact souhaite confirmer votre identité grâce à un secret partagé. Il vous a envoyé le message/indice suivant concernant ce secret.</string> + <string name="shared_secret_hint_should_not_be_empty">Votre indice ne doit pas être vide</string> + <string name="shared_secret_can_not_be_empty">Votre secret partagé ne doit pas être vide</string> + <string name="manual_verification_explanation">Comparez avec soin l\'empreinte ci-dessous avec celle de votre contact.\nPour ce faire, vous pouvez utiliser n\'importe quel moyen de communication auquel vous avez confiance : appel téléphonique, e-mail encrypté...</string> <string name="change_password">Changer de mot de passe</string> <string name="current_password">Mot de passe actuel</string> <string name="new_password">Nouveau mot de passe</string> - <string name="password_should_not_be_empty">Le mot de passe ne peut être vide</string> + <string name="password_should_not_be_empty">Le mot de passe ne doit pas être vide</string> <string name="enable_all_accounts">Activer tous les comptes</string> <string name="disable_all_accounts">Désactiver tous les comptes</string> <string name="perform_action_with">Faire une action avec</string> @@ -369,23 +407,25 @@ <string name="no_role">Aucun rôle</string> <string name="outcast">Banni</string> <string name="member">Membre</string> - <string name="advanced_mode">Mode avancé</string> + <string name="advanced_mode">Mode expert</string> <string name="grant_membership">Accorder le statut de membre</string> <string name="remove_membership">Révoquer le statut de membre</string> <string name="grant_admin_privileges">Accorder des privilèges d\'administrateur</string> <string name="remove_admin_privileges">Révoquer des privilèges d\'administrateur</string> <string name="remove_from_room">Supprimer de la conférence</string> <string name="could_not_change_affiliation">Impossible de changer l\'affiliation de %s</string> - <string name="ban_from_conference">Interdire de la conférence</string> - <string name="removing_from_public_conference">Vous essayez de supprimer %s d\'une conférence publique. La seule façon de le faire consiste à l\'interdire définitivement.</string> - <string name="ban_now">Interdire maintenant</string> + <string name="ban_from_conference">Bannir de la conférence</string> + <string name="removing_from_public_conference">Vous essayez d\'éjecter %s d\'une conférence publique. La seule façon de le faire consiste à bannir cet utilisateur définitivement.</string> + <string name="ban_now">Bannir maintenant</string> <string name="could_not_change_role">Impossible de changer le rôle de %s</string> <string name="public_conference">Conférence accessible au public</string> <string name="private_conference">Conférence privée, réservée aux membres</string> <string name="conference_options">Options de la conférence</string> - <string name="members_only">Privée (reservée aux membres)</string> + <string name="members_only">Privé, membres uniquement</string> <string name="non_anonymous">Non anonyme</string> - <string name="modified_conference_options">Options de la conférence modifiée!</string> + <string name="moderated">Modéré</string> + <string name="you_are_not_participating">Vous ne participez pas</string> + <string name="modified_conference_options">Options de la conférence modifiée !</string> <string name="could_not_modify_conference_options">Impossible de modifier les options de la conférence</string> <string name="never">Jamais</string> <string name="thirty_minutes">30 minutes</string> @@ -393,36 +433,126 @@ <string name="two_hours">2 heures</string> <string name="eight_hours">8 heures</string> <string name="until_further_notice">Jusqu\'à nouvel ordre</string> - <string name="pref_input_options">Options de saisie</string> - <string name="pref_enter_is_send">Entrée permet d\'envoyer</string> - <string name="pref_enter_is_send_summary">Utiliser la touche Entrée pour envoyer un message</string> + <string name="pref_input_options">Saisie</string> + <string name="pref_enter_is_send">Touche Entrée pour envoyer</string> + <string name="pref_enter_is_send_summary">Utiliser la touche Entrée pour envoyer un message.</string> <string name="pref_display_enter_key">Afficher la touche Entrée</string> - <string name="pref_display_enter_key_summary">Remplacer le bouton Émoticônes par un bouton Entrée</string> + <string name="pref_display_enter_key_summary">Remplacer le bouton des Émoticônes par un bouton Entrée.</string> <string name="audio">audio</string> - <string name="video">video</string> + <string name="video">vidéo</string> <string name="image">image</string> <string name="pdf_document">document PDF</string> <string name="apk">Application Android</string> <string name="vcard">Contact</string> - <string name="received_x_file">%s reçu</string> - <string name="disable_foreground_service">Cesser de garder le service au premier plan</string> + <string name="received_x_file">%s reçu(e)</string> + <string name="disable_foreground_service">Ne plus garder le service au 1er plan</string> <string name="touch_to_open_conversations">Cliquez pour ouvrir Conversations</string> - <string name="avatar_has_been_published">L\'avatar a été envoyé !</string> - <string name="sending_x_file">Envoi de %s</string> - <string name="offering_x_file">Proposition pour %s</string> - <string name="hide_offline">Cacher hors-ligne</string> + <string name="avatar_has_been_published">L\'avatar a été publié !</string> + <string name="sending_x_file">%s en cours d\'envoi</string> + <string name="offering_x_file">En train de proposer un(e) %s</string> + <string name="hide_offline">Se cacher hors-ligne</string> <string name="disable_account">Désactiver le compte</string> - <string name="contact_is_typing">%s écrit un message...</string> + <string name="contact_is_typing">%s est en train d\'écrire</string> <string name="contact_has_stopped_typing">%s a arrêté d\'écrire</string> <string name="pref_chat_states">Notifications d\'écriture</string> - <string name="pref_chat_states_summary">Permettre à votre contact de savoir que vous écrivez un message</string> + <string name="pref_chat_states_summary">Informer votre contact lorsque vous êtes en train d\'écrire un message.</string> <string name="send_location">Envoyer la position</string> <string name="show_location">Afficher la position</string> <string name="no_application_found_to_display_location">Aucune application trouvée pour afficher la position</string> <string name="location">Position</string> <string name="received_location">Position reçue</string> + <string name="title_undo_swipe_out_conversation">Conversation fermée</string> + <string name="title_undo_swipe_out_muc">Conférence quittée</string> + <string name="pref_dont_trust_system_cas_title">Ne pas utiliser les CAs système</string> + <string name="pref_dont_trust_system_cas_summary">Tous les certificats doivent être approuvés manuellement.</string> + <string name="pref_remove_trusted_certificates_title">Retirer les certificats</string> + <string name="pref_remove_trusted_certificates_summary">Supprimer les certificats approuvés manuellement.</string> + <string name="toast_no_trusted_certs">Aucun certificat approuvé manuellement</string> + <string name="dialog_manage_certs_title">Retirer les certificats</string> + <string name="dialog_manage_certs_positivebutton">Supprimer la sélection</string> + <string name="dialog_manage_certs_negativebutton">Annuler</string> + <plurals name="toast_delete_certificates"> + <item quantity="one">%d certificat supprimé</item> + <item quantity="other">%d certificats supprimés</item> + </plurals> <plurals name="select_contact"> <item quantity="one">%d contact séléctionné</item> - <item quantity="other">%d contacts séléctionnés</item> + <item quantity="other">%d contacts sélectionnés</item> + </plurals> + <string name="pref_quick_action_summary">Remplacer le bouton Envoyer par une action rapide.</string> + <string name="pref_quick_action">Action Rapide</string> + <string name="none">Aucune</string> + <string name="recently_used">Dernière utilisée</string> + <string name="choose_quick_action">Sélectionner l\'action rapide</string> + <string name="search_for_contacts_or_groups">Rechercher des contacts ou des groupes</string> + <string name="send_private_message">Envoyer un message privé</string> + <string name="user_has_left_conference">%s a quitté la conférence !</string> + <string name="username">Identifiant</string> + <string name="username_hint">Identifiant</string> + <string name="invalid_username">Ce n\'est pas un identifiant valide</string> + <string name="download_failed_server_not_found">Échec du téléchargement : impossible de trouver le serveur</string> + <string name="download_failed_file_not_found">Échec du téléchargement : impossible de trouver le fichier</string> + <string name="download_failed_could_not_connect">Échec du téléchargement : impossible de se connecter à l\'hôte</string> + <string name="account_status_tor_unavailable">Réseau Tor inaccessible</string> + <string name="server_info_broken">Détraqué</string> + <string name="pref_presence_settings">Présence</string> + <string name="pref_away_when_screen_off">Absent quand l\'écran est éteint</string> + <string name="pref_away_when_screen_off_summary">Marquer cette ressource comme absente quand l\'écran est éteint.</string> + <string name="pref_xa_on_silent_mode">Indisponible en mode silencieux</string> + <string name="pref_xa_on_silent_mode_summary">Marque cette ressource comme indisponible quand l\'appareil est en mode silencieux</string> + <string name="pref_show_connection_options">Paramètres de connexioin avancés</string> + <string name="pref_show_connection_options_summary">Montrer le nom d\'hôte et le port lors du paramétrage d\'un compte</string> + <string name="hostname_example">xmpp.example.com</string> + <string name="action_add_account_with_certificate">Ajouter un compte avec un certificat</string> + <string name="unable_to_parse_certificate">Impossible d\'analyser le certificat</string> + <string name="authenticate_with_certificate">Laisser vide pour s\'identifier avec un certificat</string> + <string name="mam_prefs">Paramètres d\'archivage</string> + <string name="server_side_mam_prefs">Paramètres d\'archivage du serveur</string> + <string name="fetching_mam_prefs">Récupération des paramètres d\'archivage en cours...</string> + <string name="unable_to_fetch_mam_prefs">Impossible de récupérer les paramètres d\'archivage</string> + <string name="captcha_ocr">Texte du captcha</string> + <string name="captcha_required">Captcha obligatoire</string> + <string name="captcha_hint">Saisissez le texte dans l\'image</string> + <string name="certificate_chain_is_not_trusted">La chaîne de certificats n\'est pas digne de confiance</string> + <string name="jid_does_not_match_certificate">L\'identifiant ne correspond pas au certificat</string> + <string name="action_renew_certificate">Renouveler le certificat</string> + <string name="error_fetching_omemo_key">Erreur lors de la récupération de la clef OMEMO !</string> + <string name="verified_omemo_key_with_certificate">Clef OMEMO vérifiée avec un certificat !</string> + <string name="device_does_not_support_certificates">Votre appareil ne supporte pas la sélection de certificats client !</string> + <string name="pref_connection_options">Connexion</string> + <string name="account_settings_hostname">Nom d\'hôte</string> + <string name="account_settings_port">Port</string> + <string name="not_a_valid_port">Ce numéro de port n\'est pas valide</string> + <string name="not_valid_hostname">Ce nom d\'hôte n\'est pas valide</string> + <string name="connected_accounts">%1$d compte(s) sur %2$d connecté(s)</string> + <plurals name="x_messages"> + <item quantity="one">%d message</item> + <item quantity="other">%d messages</item> </plurals> + <string name="shared_file_with_x">Fichier partagé avec %s</string> + <string name="shared_image_with_x">Image partagée avec %s</string> + <string name="no_storage_permission">Conversations a besoin d\'accéder au stockage externe</string> + <string name="sync_with_contacts">Synchroniser avec contacts</string> + <string name="sync_with_contacts_long">Conversations souhaite associer vos contacts XMPP avec les contacts de votre appareil, pour utiliser leur nom complet et leur avatar.\n\nConversations va uniquement lire vos contacts et les associer localement, sans les uploader sur le serveur XMPP.\n\nVotre appareil va maintenant vous demander la permission d\'accéder à vos contacts.</string> + <string name="certificate_information">Informations du certificat</string> + <string name="certificate_subject">Sujet</string> + <string name="certificate_issuer">Émetteur</string> + <string name="certificate_cn">Nom commun</string> + <string name="certificate_o">Organisation</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(Non disponible)</string> + <string name="certificate_not_found">Aucun certificat trouvé</string> + <string name="notify_on_all_messages">Notifier pour tous les messages</string> + <string name="notify_only_when_highlighted">Notifier uniquement quand surligné</string> + <string name="notify_never">Notifications désactivées</string> + <string name="notify_paused">Notifications en pause</string> + <string name="always">Toujours</string> + <string name="automatically">Automatiquement</string> + <string name="battery_optimizations_enabled">Optimisations de batterie activées</string> + <string name="battery_optimizations_enabled_explained">Votre appareil applique sur Conversations des optimisations de batterie très strictes qui pourraient provoquer des retards dans les notifications, voire des pertes de messages.\nNous vous recommandons de les désactiver.</string> + <string name="battery_optimizations_enabled_dialog">Votre appareil applique sur Conversations des optimisations de batterie très strictes qui pourraient provoquer des retards dans les notifications, voire des pertes de messages.\nVous allez maintenant avoir la possibilité de les désactiver.</string> + <string name="disable">Désactiver</string> + <string name="selection_too_large">La zone sélectionnée est trop grande</string> + <string name="no_accounts">(Aucun compte activé)</string> + <string name="this_field_is_required">Ce champ est requis</string> </resources> diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index ff6c5eeb..3abda855 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -14,7 +14,6 @@ <string name="minutes_ago">min</string> <string name="unread_conversations">conversas sen ler</string> <string name="sending">enviando…</string> - <string name="encrypted_message">Descifrando mensaxe. Agarda uns intres…</string> <string name="nick_in_use">O apodo xa está en uso</string> <string name="moderator">Moderador</string> <string name="participant">Participante</string> @@ -42,9 +41,7 @@ <string name="clear_conversation_history">Limpar historial de conversa</string> <string name="clear_histor_msg">¿Queres borrar todas as mensaxes desta conversa?\n\n<b>Ollo:</b> Isto non afectará ás mensaxes gardadas noutros dispositivos ou servidores.</string> <string name="delete_messages">Borrar mensaxes</string> - <string name="also_end_conversation">Terminar esta conversa máis tarde</string> <string name="choose_presence">Selecciona recurso del contacto</string> - <string name="send_plain_text_message">Enviar mensaxe de texto</string> <string name="send_otr_message">Enviar mensaxe cifrado con OTR</string> <string name="send_pgp_message">Enviar mensaxe cifrado con OpenPGP</string> <string name="your_nick_has_been_changed">Modificouse o teu apodo</string> @@ -57,26 +54,22 @@ <string name="offering">ofrecendo…</string> <string name="no_pgp_key">Clave OpenPGP non atopada</string> <string name="contact_has_no_pgp_key">Conversations non foi quen de cifrar as túas mensaxes porque o teu contactos non está anunciando a súa clave pública.\n\n<small>Por favor, pídelle ao teu contacto que configure OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Mensaxe cifrado recibido. Pulsa para ver.</i></string> <string name="pref_xmpp_resource">Recurso</string> <string name="pref_xmpp_resource_summary">O nome que identifica o cliente que estás a empregar</string> <string name="pref_accept_files">Aceptar arquivos</string> <string name="pref_accept_files_size_summary">De forma automática aceptar arquivos menores de…</string> <string name="pref_notification_settings">Axustes de notificación</string> + <string name="pref_accept_files_summary">De forma automática aceptar arquivos menores de…</string> <string name="pref_notifications">Notificacións</string> <string name="pref_notifications_summary">Notifica cuando chega unha nova mensaxe</string> <string name="pref_vibrate">Tremer</string> <string name="pref_vibrate_summary">Treme cando chega unha novo mensaxe</string> <string name="pref_sound">Son</string> <string name="pref_sound_summary">Reproduce un ton ca notificación</string> - <string name="pref_conference_notifications">Notificacións de conferencia</string> - <string name="pref_conference_notifications_summary">Siempre notifica cuando chega unha mensaxe de conferencia e non solo cuando chega unha mensaxe destacada</string> <string name="pref_notification_grace_period">Notificacións Carbons</string> <string name="pref_notification_grace_period_summary">Deshabilita as notificacións durante un corto periodo de tiempo despois de recibir a copia da mensaxe carbón</string> - <string name="pref_advanced_options">Opcións avanzadas</string> <string name="pref_never_send_crash">Nunca enviar informe de erros</string> <string name="pref_never_send_crash_summary">Enviando volcados de pilas axudas al desenrolo de Conversations</string> - <string name="pref_ui_options">Opcións de interfaz</string> <string name="openpgp_error">OpenKeychain reportou un erro</string> <string name="error_decrypting_file">I/O Erro descifrando arquivo</string> <string name="accept">Aceptar</string> @@ -109,7 +102,6 @@ <string name="account_status_regis_conflict">O identificador xa está en uso</string> <string name="account_status_regis_success">Rexistro completado</string> <string name="account_status_regis_not_sup">O servidor non soporta rexistros</string> - <string name="encryption_choice_none">Texto plano</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> <string name="mgmt_account_edit">Editar conta</string> diff --git a/src/main/res/values-id/strings.xml b/src/main/res/values-id/strings.xml index 6f2d967f..eae96d30 100644 --- a/src/main/res/values-id/strings.xml +++ b/src/main/res/values-id/strings.xml @@ -9,7 +9,6 @@ <string name="action_secure">Amankan Percakapan</string> <string name="action_add_account">Tambah Akun</string> <string name="action_edit_contact">Ubah Nama</string> - <string name="action_add_phone_book">Tambah ke buku telepon</string> <string name="action_delete_contact">Hapus dari roster</string> <string name="action_block_contact">Blokir kontak</string> <string name="action_unblock_contact">Batal blokir kontak</string> @@ -28,7 +27,6 @@ <string name="minutes_ago">%d min lalu</string> <string name="unread_conversations">Percakapan belum dibaca</string> <string name="sending">mengirim...</string> - <string name="encrypted_message">Menerjemahkan pesan. Tunggu sebentar...</string> <string name="nick_in_use">Nick ini sudah digunakan</string> <string name="admin">Administrator</string> <string name="owner">Pemilik</string> @@ -74,9 +72,7 @@ <string name="clear_conversation_history">Hapus Riwayat Percakapan</string> <string name="clear_histor_msg">Apakah Anda ingin menghapus semua pesan dalam Percakapan ini\n\n<b>Peringatan:</b>ini tidak akan mempengaruhi pesan yang disimpan pada perangkat atau server lain.</string> <string name="delete_messages">Hapus pesan</string> - <string name="also_end_conversation">Akhiri percakapan setelahnya</string> <string name="choose_presence">Pilih kehadiran untuk kontak</string> - <string name="send_plain_text_message">Kirim pesan teks biasa</string> <string name="send_otr_message">Kirim pesan terenskripsi OTR</string> <string name="send_pgp_message">Kirim pesan terenskripsi OpenPGP</string> <string name="your_nick_has_been_changed">Nick kamu telah dirubah</string> @@ -92,29 +88,23 @@ <string name="contact_has_no_pgp_key">Conversations tidak dapat mengenkripsi pesan Anda karena kontak tidak mengumumkan kunci publiknya.\n\n<small>Silakan meminta kontak Anda untuk menyetel OpenPGP</small></string> <string name="no_pgp_keys">Tidak ada kunci OpenPGP ditemukan</string> <string name="contacts_have_no_pgp_keys">Percakapan tidak dapat mengenkripsi pesan Anda karena kontak tidak mengumumkan kunci publik mereka.\n\n<small>Silakan meminta kontak Anda untuk setup OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Pesan terenkripsi diterima. Sentuh untuk membongkar dan melihatnya.</i></string> <string name="pref_general">Umum</string> <string name="pref_xmpp_resource">XMPP resource</string> <string name="pref_xmpp_resource_summary">Identifikasi nama klien ini dengan</string> <string name="pref_accept_files">Terima berkas</string> <string name="pref_accept_files_summary">Otomatis menerima berkas lebih kecil dari...</string> - <string name="pref_notification_settings">Pengaturan Notifikasi</string> <string name="pref_notifications">Notifikasi</string> <string name="pref_notifications_summary">Notifikasikan jika pesan baru tiba</string> <string name="pref_vibrate">Getar</string> <string name="pref_vibrate_summary">Juga aktifkan getaran bila pesan baru tiba</string> <string name="pref_sound">Suara</string> <string name="pref_sound_summary">mainkan suara saat menerima notifikasi</string> - <string name="pref_conference_notifications">Notifikasi Conference</string> - <string name="pref_conference_notifications_summary">Selalu memberitahukan bila pesan conference baru diterima daripada hanya dicetak tebal</string> <string name="pref_notification_grace_period">Tenggang waktu pemberitahuan</string> <string name="pref_notification_grace_period_summary">Nonaktifkan pemberitahuan untuk waktu yang singkat setelah salinan diterima</string> - <string name="pref_advanced_options">Opsi Lanjutan</string> <string name="pref_never_send_crash">Jangan kirim laporan kerusakan</string> <string name="pref_never_send_crash_summary">Dengan mengirimkan kesalahan Anda membantu pengembangan Aplikasi Conversations</string> <string name="pref_confirm_messages">Konfirmasi Pesan</string> <string name="pref_confirm_messages_summary">Biarkan kontak Anda tahu kapan Anda telah menerima dan membaca pesan</string> - <string name="pref_ui_options">Opsi Tampilan</string> <string name="openpgp_error">OpenKeychain melaporkan kesalahan</string> <string name="error_decrypting_file">I/O Error menerjemahkan berkas</string> <string name="accept">Menerima</string> @@ -149,7 +139,6 @@ <string name="account_status_regis_not_sup">Server tidak mendukung pendaftaran akun.</string> <string name="account_status_security_error">Kesalahan keamanan</string> <string name="account_status_incompatible_server">Server tidak cocok</string> - <string name="encryption_choice_none">Teks biasa</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> <string name="mgmt_account_edit">Ubah akun</string> @@ -170,7 +159,6 @@ <string name="passwords_do_not_match">Password tidak sama</string> <string name="invalid_jid">Jabber ID tidak valid</string> <string name="error_out_of_memory">Memori habis. Gambar terlalu besar</string> - <string name="add_phone_book_text">Apakah anda ingin menambahkan %s ke daftar kontak anda?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">bebas untuk chatting</string> <string name="contact_status_away">pergi</string> @@ -186,7 +174,6 @@ <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> <string name="server_info_available">tersedia</string> <string name="server_info_unavailable">tidak tersedia</string> <string name="missing_public_keys">Pemberitahuan kunci publik tidak ditemukan</string> @@ -204,6 +191,8 @@ <string name="reception_failed">Penerimaan gagal</string> <string name="your_fingerprint">Fingerprint Anda</string> <string name="otr_fingerprint">OTR fingerprint</string> + <string name="other_devices">Perangkat lainnya</string> + <string name="done">Selesai</string> <string name="verify">Verifikasi</string> <string name="decrypt">Deskripsi</string> <string name="conferences">Conferences</string> @@ -249,7 +238,6 @@ <string name="skip">Lewati</string> <string name="disable_notifications">Nonaktifkan notifikasi</string> <string name="disable_notifications_for_this_conversation">Nonaktifkan notifikasi untuk percakapan ini</string> - <string name="notifications_disabled">Notifikasi telah dimatikan</string> <string name="enable">Aktifkan</string> <string name="conference_requires_password">Conference membutuhkan password</string> <string name="enter_password">Masukan password</string> @@ -260,12 +248,10 @@ <string name="sure_delete_fingerprint">Apakah anda yakin menghapus sidik jari?</string> <string name="ignore">Abaikan</string> <string name="without_mutual_presence_updates"><b>Perhatian</b> Mengirim ini tanpa kehadiran sesama pembaruan bisa menyebabkan masalah tak terduga.\n\n<small>Pergi ke kontak untuk memverifikasi langganan kehadiran anda.</small></string> - <string name="pref_encryption_settings">Pengaturan enskripsi</string> <string name="pref_force_encryption">Paksa enskripsi end-to-end</string> <string name="pref_force_encryption_summary">Selalu mengirim pesan terenkripsi (kecuali untuk conferences)</string> <string name="pref_dont_save_encrypted">jangan simpan pesan terenskripsi</string> <string name="pref_dont_save_encrypted_summary">Peringatan: Hal ini bisa mengakibatkan hilangnya pesan</string> - <string name="pref_expert_options">Pengaturan lanjutan</string> <string name="pref_expert_options_summary">Harap berhati-hati dengan ini</string> <string name="title_activity_about">Tentang Conversations</string> <string name="pref_about_conversations_summary">Build dan informasi lisensi</string> @@ -289,10 +275,12 @@ <string name="conference_kicked">Anda telah ditendang dari conference ini</string> <string name="using_account">menggunakan akun %s</string> <string name="not_connected_try_again">Anda tidak terhubung. Coba lagi nanti</string> + <string name="check_x_filesize">Cek %s ukuran</string> <string name="message_options">Opsi pesan</string> <string name="copy_text">Salin teks</string> <string name="copy_original_url">Salin URL asli</string> <string name="send_again">Kirim lagi</string> + <string name="file_url">URL Berkas</string> <string name="message_text">Pesan teks</string> <string name="url_copied_to_clipboard">URL disalin ke clipboard</string> <string name="message_copied_to_clipboard">Pesan disalin ke clipboard</string> @@ -304,7 +292,6 @@ <string name="verify_otr">Verifikasi OTR</string> <string name="remote_fingerprint">Remote Sidik jari</string> <string name="scan">pindai</string> - <string name="or_touch_phones">(atau menyentuh telepon)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Petunjuk atau Pertanyaan</string> <string name="shared_secret_secret">Rahasia bersama</string> @@ -347,6 +334,7 @@ <string name="reset">Ulang</string> <string name="account_image_description">Avatar akun</string> <string name="copy_otr_clipboard_description">Salin OTR fingerprint ke clipboard</string> + <string name="clear_other_devices">Bersihkan perangkat</string> <string name="fetching_history_from_server">Mengambil data dari server</string> <string name="no_more_history_on_server">Tidak ada data lagi di server</string> <string name="updating">Merubah...</string> @@ -384,7 +372,6 @@ <string name="public_conference">Conference umum</string> <string name="private_conference">Rahasia, hanya member conference</string> <string name="conference_options">Opsi conference</string> - <string name="members_only">Rahasia (Hanya member)</string> <string name="non_anonymous">Non Anonymous</string> <string name="modified_conference_options">Opsi conference dimodifikasi!</string> <string name="could_not_modify_conference_options">Tidak dapat merubah pengaturan conference</string> @@ -394,7 +381,6 @@ <string name="two_hours">2 jam</string> <string name="eight_hours">8 jam</string> <string name="until_further_notice">Sampai pemberitahuan selanjutnya</string> - <string name="pref_input_options">Opsi input</string> <string name="pref_enter_is_send">Enter untuk mengirim</string> <string name="pref_enter_is_send_summary">Gunakan enter untuk mengrim pesan</string> <string name="pref_display_enter_key">Tampilkan masukan kunci</string> @@ -413,7 +399,6 @@ <string name="offering_x_file">Menawarkan %s</string> <string name="hide_offline">Sembunyikan Offline</string> <string name="disable_account">Nonaktifkan Akun</string> - <string name="contact_is_typing">%s sedang mengetik...</string> <string name="contact_has_stopped_typing">%s telah berhenti mengetik</string> <string name="pref_chat_states">Notifikasi ketik pesan</string> <string name="pref_chat_states_summary">Biarkan kontak Anda tahu ketika Anda sedang menulis pesan baru</string> @@ -424,7 +409,6 @@ <string name="received_location">Lokasi yang diterima</string> <string name="title_undo_swipe_out_conversation">Percakapan tertutup</string> <string name="title_undo_swipe_out_muc">Tinggalkan conference</string> - <string name="pref_certificate_options">Opsi Sertifikat</string> <string name="pref_dont_trust_system_cas_title">Jangan percaya sistem CA</string> <string name="pref_dont_trust_system_cas_summary">Semua sertifikat harus disetujui secara manual</string> <string name="pref_remove_trusted_certificates_title">Hapus sertifikat</string> @@ -444,4 +428,13 @@ <string name="none">Tak satupun</string> <string name="recently_used">Maling sering digunakan</string> <string name="choose_quick_action">Pilih aksi cepat</string> + <string name="search_for_contacts_or_groups">Cari grup atau daftar kontak</string> + <string name="send_private_message">Kirim pesan pribadi</string> + <string name="user_has_left_conference">%s meninggalkan conference!</string> + <string name="username">Username</string> + <string name="username_hint">Username</string> + <string name="invalid_username">Username ini tidak valid</string> + <string name="download_failed_server_not_found">Unduhan gagal: Server tidak ditemukan</string> + <string name="download_failed_file_not_found">Unduh gagal: Berkas tidak ditemukan</string> + <string name="download_failed_could_not_connect">Unduhan gagal: Tidak dapat terhubung ke host</string> </resources> diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 8397b043..5fac85d0 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -28,7 +28,8 @@ <string name="minutes_ago">%d min fa</string> <string name="unread_conversations">Conversazioni non lette</string> <string name="sending">invio…</string> - <string name="encrypted_message">Decifrazione del messaggio. Attendere prego…</string> + <string name="message_decrypting">Decifrazione messaggio. Attendere prego...</string> + <string name="pgp_message">Messaggio cifrato con OpenPGP</string> <string name="nick_in_use">Nome utente già in uso</string> <string name="admin">Amministratore</string> <string name="owner">Proprietario</string> @@ -60,7 +61,7 @@ <string name="crash_report_title">Errore di Conversations</string> <string name="crash_report_message">Se scegli di inviare una segnalazione dell’errore aiuterai lo sviluppo di Conversations\n<b>Attenzione:</b> Questo utilizzerà il tuo account XMPP per inviare la segnalazione agli sviluppatori.</string> <string name="send_now">Invia adesso</string> - <string name="send_never">Non chiedere mai più</string> + <string name="send_never">Non chiedere più</string> <string name="problem_connecting_to_account">Impossibile collegarsi tramite questo utente</string> <string name="problem_connecting_to_accounts">Impossibile collegarsi tramite più utenti</string> <string name="touch_to_fix">Tocca qui per gestire i tuoi utenti</string> @@ -74,10 +75,12 @@ <string name="clear_conversation_history">Pulisci la cronologia della Conversazione</string> <string name="clear_histor_msg">Vuoi cancellare tutti i messaggi di questa Conversazione?\n\n<b>Attenzione:</b> Questo non influenzerà i messaggi presenti su altri dispositivi o server.</string> <string name="delete_messages">Elimina messaggi</string> - <string name="also_end_conversation">Termina questa conversazione in seguito</string> + <string name="also_end_conversation">Termina questa conversazione successivamente</string> <string name="choose_presence">Choose presence to contact</string> - <string name="send_plain_text_message">Messaggio non cifrato</string> + <string name="send_unencrypted_message">Invia messaggio non cifrato</string> <string name="send_otr_message">Messaggio OTR</string> + <string name="send_omemo_message">Invia messaggio cifrato OMEMO</string> + <string name="send_omemo_x509_message">Invia messaggio cifrato v\\OMEMO</string> <string name="send_pgp_message">Messaggio OpenPGP</string> <string name="your_nick_has_been_changed">Il tuo nome utente è stato cambiato</string> <string name="send_unencrypted">Invia non cifrato</string> @@ -86,6 +89,7 @@ <string name="openkeychain_required_long">Conversations usa una app di terze parti chiamata <b>OpenKeychain</b> per cifrare e decifrare i messaggi per gestire le tue chiavi pubbliche.\n\nOpenKeychain è rilasciato secondo i termini della GPLv3 ed è disponibile sia su F-Droid, che su Google Play.\n\n<small>(Riavvia Conversations in seguito.)</small></string> <string name="restart">Riavvia</string> <string name="install">Installa</string> + <string name="openkeychain_not_installed">Per favore installa OpenKeychain</string> <string name="offering">offrendo…</string> <string name="waiting">in attesa…</string> <string name="no_pgp_key">Nessuna chiave OpenPGP trovata</string> @@ -97,7 +101,7 @@ <string name="pref_xmpp_resource">Risorsa XMPP</string> <string name="pref_xmpp_resource_summary">Il nome con il quale questo client si identifica</string> <string name="pref_accept_files">Accetta i file</string> - <string name="pref_accept_files_size_summary">Accetta automaticamente i file più piccoli di…</string> + <string name="pref_accept_files_summary">Accetta automaticamente i file più piccoli di…</string> <string name="pref_notification_settings">Impostazioni di Notifica</string> <string name="pref_notifications">Notifiche</string> <string name="pref_notifications_summary">Notifica quando arriva un nuovo messaggio</string> @@ -105,8 +109,6 @@ <string name="pref_vibrate_summary">Vibra anche quando arriva un nuovo messaggio</string> <string name="pref_sound">Suono</string> <string name="pref_sound_summary">Riproduci una suoneria con la notifica</string> - <string name="pref_conference_notifications">Notifiche Conferenze</string> - <string name="pref_conference_notifications_summary">Notifica sempre quando arriva un nuovo messaggio da una conferenza, invece che solo quando in primo piano</string> <string name="pref_notification_grace_period">Periodo tra notifiche</string> <string name="pref_notification_grace_period_summary">Disabilita le notifiche per un breve lasso di tempo dopo che un messaggio è stato ricevuto</string> <string name="pref_advanced_options">Opzioni Avanzate</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">Il Server non supporta la registrazione</string> <string name="account_status_security_error">Errore di sicurezza</string> <string name="account_status_incompatible_server">Server non compatibile</string> - <string name="encryption_choice_none">Testo semplice</string> + <string name="encryption_choice_unencrypted">Non cifrato</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Modifica utente</string> <string name="mgmt_account_delete">Elimina utente</string> <string name="mgmt_account_disable">Disabilita temporaneamente</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">Le Password non corrispondono</string> <string name="invalid_jid">Questo non è un ID Jabber valido</string> <string name="error_out_of_memory">Memoria esaurita. L’immagine è tropppo grande</string> - <string name="add_phone_book_text">Vuoi aggiungere %s alla rubrica del telefono?</string> + <string name="add_phone_book_text">Vuoi aggiungere %s alla tua rubrica?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">vuole chattare</string> <string name="contact_status_away">assente</string> @@ -186,7 +189,8 @@ <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> <string name="server_info_available">disponibile</string> <string name="server_info_unavailable">non disponibile</string> <string name="missing_public_keys">Annuncio chiave pubblica non effettuato</string> @@ -204,17 +208,28 @@ <string name="reception_failed">Ricezione fallita</string> <string name="your_fingerprint">La tua impronta</string> <string name="otr_fingerprint">Impronta OTR</string> + <string name="omemo_fingerprint">Fingerprint OMEMO</string> + <string name="omemo_fingerprint_x509">v\\OMEMO fingerprint</string> + <string name="omemo_fingerprint_selected_message">Fingerprint OMEMO del messaggio</string> + <string name="omemo_fingerprint_x509_selected_message">Fingerprint v\\OMEMO del messaggio</string> + <string name="this_device_omemo_fingerprint">Propria fingerprint OMEMO</string> + <string name="other_devices">Altri dispositivi</string> + <string name="trust_omemo_fingerprints">Fidati delle Fingerprint OMEMO</string> + <string name="fetching_keys">Ricezione chiavi...</string> + <string name="done">Fatto</string> <string name="verify">Verifica</string> <string name="decrypt">Decripta</string> <string name="conferences">Conferenze</string> <string name="search">Cerca</string> <string name="create_contact">Crea Contatto</string> + <string name="enter_contact">Inserisci contatto</string> <string name="join_conference">Entra in Conferenza</string> <string name="delete_contact">Elimina Contatto</string> <string name="view_contact_details">Mostra dettagli contatto</string> <string name="block_contact">Blocca contatto</string> <string name="unblock_contact">Sblocca contatto</string> <string name="create">Crea</string> + <string name="select">Seleziona</string> <string name="contact_already_exists">Il contatto esiste già</string> <string name="join">Entra</string> <string name="conference_address">Indirizzo conferenza</string> @@ -249,7 +264,6 @@ <string name="skip">Salta</string> <string name="disable_notifications">Disabilita le notifiche</string> <string name="disable_notifications_for_this_conversation">Disabilita le notifiche per questa conversazione</string> - <string name="notifications_disabled">Le notifiche sono disabilitate</string> <string name="enable">Abilita</string> <string name="conference_requires_password">La conferenza richiede una password</string> <string name="enter_password">Inserisci la password</string> @@ -284,15 +298,19 @@ <string name="pref_conference_name">Nome della conferenza</string> <string name="pref_conference_name_summary">Usa il soggetto della stanza al posto del JID per identificare le conferenze</string> <string name="toast_message_otr_fingerprint">Impronta OTR copiata!</string> + <string name="toast_message_omemo_fingerprint">Fingerprint OMEMO copiata negli appunti|</string> <string name="conference_banned">Sei stato bandito da questa conferenza</string> <string name="conference_members_only">Questa conferenza è solo per membri</string> <string name="conference_kicked">Sei stato buttato fuori dalla conferenza</string> <string name="using_account">usando l’utente %s</string> + <string name="checking_x">Controllo %s su host HTTP</string> <string name="not_connected_try_again">Non sei connesso. Riprova più tardi</string> + <string name="check_x_filesize">Controllo dimensione %s</string> <string name="message_options">Opzioni del messaggio</string> <string name="copy_text">Copia testo</string> <string name="copy_original_url">Copia URL originale</string> <string name="send_again">Invia di nuovo</string> + <string name="file_url">URL del file</string> <string name="message_text">Messaggio di testo</string> <string name="url_copied_to_clipboard">URL copiato</string> <string name="message_copied_to_clipboard">Messaggio copiato</string> @@ -304,7 +322,6 @@ <string name="verify_otr">Verifica OTR</string> <string name="remote_fingerprint">Impronta remota</string> <string name="scan">scan</string> - <string name="or_touch_phones">(o fai toccare i dispositivi)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Suggerimento o Domanda</string> <string name="shared_secret_secret">Segreto condiviso</string> @@ -321,6 +338,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Mantieni il servizio in primo piano</string> <string name="pref_keep_foreground_service_summary">Evita che il sistema operativo chiuda la connessione</string> + <string name="pref_export_logs">Esporta Log</string> + <string name="pref_export_logs_summary">Scrivi i log su card SD</string> + <string name="notification_export_logs_title">Scrittura log su card SD</string> <string name="choose_file">Scegli file</string> <string name="receiving_x_file">Ricezione di %1$s file (%2$d%% completato)</string> <string name="download_x_file">Scarica %s</string> @@ -347,6 +367,17 @@ <string name="reset">Reset</string> <string name="account_image_description">Avatar utente</string> <string name="copy_otr_clipboard_description">Copia impronta OTR</string> + <string name="copy_omemo_clipboard_description">Copia fingerprint OMEMO negli appunti</string> + <string name="regenerate_omemo_key">Rigenera chiave OMEMO</string> + <string name="wipe_omemo_pep">Cancella altri dispositivi da PEP</string> + <string name="clear_other_devices">Pulisci dispositivi</string> + <string name="clear_other_devices_desc">Sei sicuro/a di voler rimuovere tutti gli altri dispositivi dall\'annuncio OMEMO? La prossima volta che si connetteranno si riannunceranno, ma potrebbero non ricevere i messaggi inviati nel frattempo.</string> + <string name="purge_key">Elimina chiave</string> + <string name="purge_key_desc_part1">Sei sicuro/a di voler cancellare questa chiave?</string> + <string name="purge_key_desc_part2">Verrà considerata irreversibilmente compromessa, e non potrai più creare una sessione con essa.</string> + <string name="error_no_keys_to_trust_server_error">Non ci sono chiavi utilizzabili per questo contatto.\nLa raccolta di nuove chiavi dal server è fallita. Forse qualcosa non va con il tuo server dei contatti.</string> + <string name="error_no_keys_to_trust">Non ci sono chiavi usabili per questo contatto. Se hai cancellato qualsiasi loro chiave, devono generarne di nuove.</string> + <string name="error_trustkeys_title">Errore</string> <string name="fetching_history_from_server">Caricamento della cronologia dal server</string> <string name="no_more_history_on_server">Fine cronologia sul server</string> <string name="updating">Caricamento…</string> @@ -384,8 +415,10 @@ <string name="public_conference">Conferenza pubblicamente accessibile</string> <string name="private_conference">Conferenza privata</string> <string name="conference_options">Opzioni conferenza</string> - <string name="members_only">Privata (solo membri)</string> + <string name="members_only">Privato, solo membri</string> <string name="non_anonymous">Non anonimo</string> + <string name="moderated">Moderata</string> + <string name="you_are_not_participating">Non stai partecipando</string> <string name="modified_conference_options">Modificate le opzioni della conferenza!</string> <string name="could_not_modify_conference_options">Impossibile modificare opzioni conferenza</string> <string name="never">Mai</string> @@ -395,7 +428,7 @@ <string name="eight_hours">8 ore</string> <string name="until_further_notice">Fino a nuovo avviso</string> <string name="pref_input_options">Opzioni di ingresso</string> - <string name="pref_enter_is_send">Invio invia</string> + <string name="pref_enter_is_send">Invio spedisce</string> <string name="pref_enter_is_send_summary">Il tasto invio spedisce il messaggio</string> <string name="pref_display_enter_key">Mostra il tasto invio</string> <string name="pref_display_enter_key_summary">Cambia il tasto delle faccine nel tastodi invio</string> @@ -406,6 +439,7 @@ <string name="apk">Applicazione Android</string> <string name="vcard">Contatto</string> <string name="received_x_file">Ricevuto %s</string> + <string name="disable_foreground_service">Disabilita i servizi in background</string> <string name="touch_to_open_conversations">Tocca per avviare Conversations</string> <string name="avatar_has_been_published">Il tuo avatar è stato pubblicato!</string> <string name="sending_x_file">Invio %s</string> @@ -414,6 +448,7 @@ <string name="disable_account">Disabilita l\'account</string> <string name="contact_is_typing">%s sta digitando...</string> <string name="contact_has_stopped_typing">%s ha smesso di digitare</string> + <string name="pref_chat_states">Notifiche di composizione</string> <string name="pref_chat_states_summary">Permetti al tuo contatto di vedere quando stai digitando</string> <string name="send_location">Invia la posizione</string> <string name="show_location">Mostra la posizione</string> @@ -422,7 +457,6 @@ <string name="received_location">Posizione ricevuta</string> <string name="title_undo_swipe_out_conversation">Conversazione interrotta</string> <string name="title_undo_swipe_out_muc">Conferenza terminata</string> - <string name="pref_certificate_options">Opzioni per i certificati</string> <string name="pref_dont_trust_system_cas_title">Non ti fidare delle CA di sistema</string> <string name="pref_dont_trust_system_cas_summary">Tutti i certificati devono essere accettati manualmente</string> <string name="pref_remove_trusted_certificates_title">Elimina i certificati</string> @@ -439,4 +473,71 @@ <item quantity="one">Seleziona il %d contatto</item> <item quantity="other">Selezionati %d contatti</item> </plurals> + <string name="pref_quick_action_summary">Sostituisci il tasto invio con un\'azione rapida</string> + <string name="pref_quick_action">Azione Rapida</string> + <string name="none">Nessuno</string> + <string name="recently_used">Usati recentemente</string> + <string name="choose_quick_action">Scegli azione rapida</string> + <string name="search_for_contacts_or_groups">Cerca contatti o gruppi</string> + <string name="send_private_message">Invia messaggio privato</string> + <string name="user_has_left_conference">%s ha abbandonato la conferenza!</string> + <string name="username">Utente</string> + <string name="username_hint">Utente</string> + <string name="invalid_username">Questo non è un nome utente valido</string> + <string name="download_failed_server_not_found">Download fallito: Server non trovato</string> + <string name="download_failed_file_not_found">Download fallito: File non trovato</string> + <string name="download_failed_could_not_connect">Download fallito: Impossibile connettersi all\'host</string> + <string name="account_status_tor_unavailable">Rete Tor non disponibile</string> + <string name="server_info_broken">Rotto</string> + <string name="pref_presence_settings">Impostazioni presenza</string> + <string name="pref_away_when_screen_off">\"Non disponibile\" a schermo spento</string> + <string name="pref_away_when_screen_off_summary">Imposta la tua risorsa come non disponibile quando lo schermo è spento</string> + <string name="pref_xa_on_silent_mode">Non disponibile in modalità silenzioso</string> + <string name="pref_xa_on_silent_mode_summary">Imposta la tua risorsa come non disponibile quando il dispositivo è in modalità silenziosa</string> + <string name="action_add_account_with_certificate">Aggiungi account con certificato</string> + <string name="unable_to_parse_certificate">Impossibile analizzare il certificato</string> + <string name="authenticate_with_certificate">Lasciare vuoto per autenticarsi con certificato</string> + <string name="captcha_ocr">Testo captcha</string> + <string name="captcha_required">Captcha richiest</string> + <string name="captcha_hint">inserire il testo dall\'immagine</string> + <string name="certificate_chain_is_not_trusted">Catena di certificati non affidabile</string> + <string name="jid_does_not_match_certificate">L\'ID Jabber non corrisponde al certificato</string> + <string name="action_renew_certificate">Rinnova certificato</string> + <string name="error_fetching_omemo_key">Errore ricezione chiave OMEMO!</string> + <string name="verified_omemo_key_with_certificate">Chiave OMEMO verificata con certificato!</string> + <string name="device_does_not_support_certificates">Il tuo dispositivo non supporta la selezione di certificati utente!</string> + <string name="pref_connection_options">Opzioni di connessione</string> + <string name="account_settings_hostname">Nome host</string> + <string name="account_settings_port">Porta</string> + <string name="not_a_valid_port">Questo non è un numero di porta valido</string> + <string name="not_valid_hostname">Questo non è un nome host valido</string> + <string name="connected_accounts">%1$d su %2$d account connessi</string> + <plurals name="x_messages"> + <item quantity="one">%d messaggio</item> + <item quantity="other">%d messaggi</item> + </plurals> + <string name="shared_file_with_x">Condividi file con %s</string> + <string name="shared_image_with_x">Immagine condivisa con %s</string> + <string name="no_storage_permission">Conversations necessita di accesso alla memoria esterna</string> + <string name="sync_with_contacts">Sincronizza con i contatti</string> + <string name="sync_with_contacts_long">Conversations vuole confrontare il tuo roster XMPP con i tuoi contatti per mostrare i loro nomi ed avatar.\n\nConversations leggerà solamente i contatti e li confronterà localmente senza caricarli sul server.\n\nTi verrà ora chiesto il permesso di accedere ai tuoi contatti.</string> + <string name="certificate_information">Informazioni del certificato</string> + <string name="certificate_subject">Soggetto</string> + <string name="certificate_issuer">Emittente</string> + <string name="certificate_cn">Nome comune</string> + <string name="certificate_o">Organizzazione</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(Non disponibile)</string> + <string name="certificate_not_found">Nessun certificato trovato</string> + <string name="notify_on_all_messages">Notifica per tutti i messaggi</string> + <string name="notify_only_when_highlighted">Notifica solo quando evidenziato</string> + <string name="notify_never">Notifiche disabilitate</string> + <string name="notify_paused">Notifiche in pausa</string> + <string name="always">Sempre</string> + <string name="automatically">Automaticamente</string> + <string name="battery_optimizations_enabled">Ottimizzazioni batteria abilitate</string> + <string name="battery_optimizations_enabled_explained">Il tuo dispositivo sta facendo delle ingenti ottimizzazioni della batteria per Conversations che potrebbero portare ritardi alle notifiche o anche perdita di messaggi.\nSi consiglia di disabilitarle.</string> + <string name="battery_optimizations_enabled_dialog">Il tuo dispositivo sta facendo delle ingenti ottimizzazioni della batteria per Conversations che potrebbero portare ritardi alle notifiche o anche perdita di messaggi.\n\nTi verrà ora chiesto di disabilitarle.</string> + <string name="disable">Disabilita</string> + <string name="selection_too_large">L\'area selezionata è troppo grande</string> </resources> diff --git a/src/main/res/values-iw/strings.xml b/src/main/res/values-iw/strings.xml index cc12c7d0..3f52ad4c 100644 --- a/src/main/res/values-iw/strings.xml +++ b/src/main/res/values-iw/strings.xml @@ -1,16 +1,19 @@ <?xml version='1.0' encoding='UTF-8'?> <resources> <string name="action_settings">הגדרות</string> - <string name="action_add">דיון חדש</string> + <string name="action_add">שיחה חדשה</string> <string name="action_accounts">נהל חשבונות</string> - <string name="action_end_conversation">סיים את דיון זה</string> + <string name="action_end_conversation">סיים שיחה זו</string> <string name="action_contact_details">פרטי איש קשר</string> <string name="action_muc_details">פרטי ועידה</string> <string name="action_secure">דיון מאובטח</string> <string name="action_add_account">הוסף חשבון</string> <string name="action_edit_contact">ערוך שם</string> - <string name="action_add_phone_book">הוסף אל פנקס טלפונים</string> - <string name="action_delete_contact">מחק מתוך רשימה</string> + <string name="action_delete_contact">מחק מרשימת אנשי הקשר</string> + <string name="action_block_contact">חסום איש קשר</string> + <string name="action_unblock_contact">בטל חסימת איש קשר</string> + <string name="action_block_domain">חסום דומיין</string> + <string name="action_unblock_domain">בטל חסימת דומיין</string> <string name="title_activity_manage_accounts">נהל חשבונות</string> <string name="title_activity_settings">הגדרות</string> <string name="title_activity_conference_details">פרטי ועידה</string> @@ -18,89 +21,98 @@ <string name="title_activity_sharewith">שתף בעזרת Conversations</string> <string name="title_activity_start_conversation">התחל דיון</string> <string name="title_activity_choose_contact">בחר איש קשר</string> - <string name="just_now">רק כעת</string> - <string name="minute_ago">לפני דקה 1</string> + <string name="title_activity_block_list">רשימת חסימה</string> + <string name="just_now">ממש עכשיו</string> + <string name="minute_ago">לפני דקה</string> <string name="minutes_ago">לפני %d דקות</string> - <string name="unread_conversations">דיונים שלא נקראו</string> - <string name="sending">כעת שולח…</string> - <string name="encrypted_message">כעת מפענח הודעה. אנא המתן…</string> - <string name="nick_in_use">שם כינוי כבר מצוי בשימוש</string> + <string name="unread_conversations">שיחות שלא נקראו</string> + <string name="sending">שולח...</string> + <string name="message_decrypting">כעת מפענח צופן הודעה. אנא המתן…</string> + <string name="pgp_message">הודעה מוצפנת OpenPGP</string> + <string name="nick_in_use">שם כינוי כבר בשימוש</string> <string name="admin">מנהל</string> <string name="owner">בעלים</string> - <string name="moderator">אחראי</string> + <string name="moderator">אחראי (Moderator)</string> <string name="participant">משתתף</string> <string name="visitor">מבקר</string> - <string name="remove_contact_text">האם ברצונך להסיר את %s מתןך הרשימה שלך? הדיונים אשר משוייכים עם חשבון זה לא יוסרו.</string> - <string name="remove_bookmark_text">האם ברצונך להסיר את %s בתוור סימנייה? הדיונים אשר משוייכים עם סימנייה זו לא יוסרו.</string> - <string name="register_account">רשום חשבון חדש על שרת</string> - <string name="share_with">שתף בעזרת</string> - <string name="start_conversation">התחל דיון</string> + <string name="remove_contact_text">האם ברצונך להסיר את %s מתוך רשימת אנשי הקשר? השיחה המשוייכת עם איש קשר זה לא תוסר.</string> + <string name="block_contact_text">האם ברצונך לחסום קבלת הודעות מאת %s?</string> + <string name="unblock_contact_text">האם ברצונך לבטל את החסימה ולאפשר קבלת הודעות מאת %s?</string> + <string name="block_domain_text">לחסום את כל האנשים מתוך %s?</string> + <string name="unblock_domain_text">לבטל את חסימת כל האנשים מתוך %s?</string> + <string name="contact_blocked">איש קשר נחסם</string> + <string name="remove_bookmark_text">האם ברצונך להסיר את %s בתור סימנייה? הדיונים אשר משוייכים עם סימנייה זו לא יוסרו.</string> + <string name="register_account">צור חשבון חדש בשרת</string> + <string name="change_password_on_server">שינוי סיסמה בשרת</string> + <string name="share_with">שתף באמצעות...</string> + <string name="start_conversation">התחל שיחה</string> <string name="invite_contact">הזמן איש קשר</string> <string name="contacts">אנשי קשר</string> <string name="cancel">ביטול</string> + <string name="set">הגדר</string> <string name="add">הוסף</string> <string name="edit">ערוך</string> <string name="delete">מחק</string> + <string name="block">חסום</string> + <string name="unblock">בטל חסימה</string> <string name="save">שמור</string> <string name="ok">אישור</string> <string name="crash_report_title">Conversations קרסה</string> - <string name="crash_report_message">על ידי שליחת עקבות מחסנית אתה עוזר להתקדמות הפיתוח של Conversations\n<b>אזהרה:</b> זו תעשה שימוש בחשבון XMPP שלך כדי לשלוח עקבות מחסנית אל המפתח.</string> + <string name="crash_report_message">על ידי שליחת Stacktraces אתה עוזר להתקדמות הפיתוח של Conversations\n<b>אזהרה:</b> פעולה זו תעשה שימוש בחשבון XMPP שלך כדי לשלוח עקבות מחסנית אל המפתח.</string> <string name="send_now">שלח עכשיו</string> <string name="send_never">לעולם אל תשאל שוב</string> - <string name="problem_connecting_to_account">לא מסוגל להתחבר אל חשבון</string> - <string name="problem_connecting_to_accounts">לא מסוגל להתחבר אל חשבונות מרובים</string> + <string name="problem_connecting_to_account">התחברות לחשבון נכשלה</string> + <string name="problem_connecting_to_accounts">התחברות למספר חשבונות נכשלה</string> <string name="touch_to_fix">לחץ כאן כדי לנהל את החשבונות שלך</string> <string name="attach_file">צרף קובץ</string> - <string name="not_in_roster">איש קשר אינו מצוי בתוך הרשימה שלך. האם ברצונך להוסיפו?</string> + <string name="not_in_roster">איש קשר אינו מצוי בתוך רשימת אנשי הקשר שלך. האם ברצונך להוסיפו?</string> <string name="add_contact">הוסף איש קשר</string> <string name="send_failed">מסירה נכשלה</string> <string name="send_rejected">סורב</string> - <string name="preparing_image">כעת מכין תצלום לשם תמסורת</string> - <string name="action_clear_history">טהר היסטוריה</string> - <string name="clear_conversation_history">טהר היסטוריית דיונים</string> - <string name="clear_histor_msg">האם ברצונך למחוק את כל ההודעות בתוך דיון זה?\n\n<b>אזהרה:</b> זו לא תשפיע על הודעות מאוחסנות על מכשירים או שרתים אחרים.</string> + <string name="preparing_image">מכין תמונה לשליחה</string> + <string name="action_clear_history">נקה היסטוריה</string> + <string name="clear_conversation_history">נקה היסטוריית שיחה</string> + <string name="clear_histor_msg">האם ברצונך למחוק את כל ההודעות בשיחה זאת?\n\n<b>אזהרה:</b> פעולה זו לא תשפיע על הודעות מאוחסנות על מכשירים או שרתים אחרים.</string> <string name="delete_messages">מחק הודעות</string> - <string name="also_end_conversation">סיים את דיון זה לאחר מכן</string> + <string name="also_end_conversation">סיים את השיחה לאחר מכן</string> <string name="choose_presence">בחר נוכחות לאיש קשר</string> - <string name="send_plain_text_message">שלח הודעת טקסט גלוי</string> - <string name="send_otr_message">שלח הודעה מוצפנת OTR</string> - <string name="send_pgp_message">שלח הודעה מוצפנת OpenPGP</string> - <string name="your_nick_has_been_changed">שם כינוי שלך השתנה</string> - <string name="send_unencrypted">שלח לא מוצפנת</string> + <string name="send_unencrypted_message">שלח הודעה בלתי מוצפנת</string> + <string name="send_otr_message">שלח הודעה בהצפנת OTR</string> + <string name="send_omemo_message">של הודעה בהצפנת OMEMO</string> + <string name="send_pgp_message">שלח הודעה בהצפנת OpenPGP</string> + <string name="your_nick_has_been_changed">שם הכינוי שלך השתנה</string> + <string name="send_unencrypted">שלח ללא הצפנה</string> <string name="decryption_failed">פענוח נכשל. אולי אין לך את המפתח הפרטי המתאים.</string> <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Conversations מפיקה תועלת מן אפליקציית צד-שלישי הקרויה <b>OpenKeychain</b> כדי להצפין ולפענח הודעות וגם כדי לנהל את המפתחות הפומביים שלך.\n\nOpenKeychain הינה רשויה תחת GPLv3 וזמינה אצל F-Droid וגם Google Play.\n\n<small>(אנא התחל מחדש את Conversations לאחר מכן.)</small></string> + <string name="openkeychain_required_long">Conversations מסתמכת על אפליקציית צד-שלישי הקרויה <b>OpenKeychain</b> כדי להצפין ולפענח הודעות וגם כדי לנהל את המפתחות הפומביים שלך.\n\nOpenKeychain הינה רשויה תחת GPLv3 וזמינה ב F-Droid וגם ב Google Play.\n\n<small>(אנא התחל מחדש את Conversations לאחר מכן.)</small></string> <string name="restart">התחל מחדש</string> <string name="install">התקן</string> - <string name="offering">כעת מציע…</string> - <string name="waiting">כעת ממתין…</string> + <string name="openkeychain_not_installed">אנא התקן OpenKeychain</string> + <string name="offering">מציע…</string> + <string name="waiting">ממתין…</string> <string name="no_pgp_key">לא נמצא מפתח OpenPGP</string> - <string name="contact_has_no_pgp_key">Conversations אינה מסוגלת להצפין את הודעותיך משום שאיש הקשר שלך אינו מכריז על המפתח הפומבי שלו או שלה.\n\n<small>אנא בקש מאיש הקשר שלך לארגן OpenPGP.</small></string> + <string name="contact_has_no_pgp_key">Conversations אינה מסוגלת להצפין את הודעותיך משום שאיש הקשר שלך אינו מכריז על המפתח הפומבי שלו או שלה.\n\n<small>אנא בקש מאיש הקשר שלך להגדיר את OpenPGP.</small></string> <string name="no_pgp_keys">לא נמצאו מפתחות OpenPGP</string> <string name="contacts_have_no_pgp_keys">Conversations אינה מסוגלת להצפין את הודעותיך משום שאנשי הקשר שלך אינם מכריזים על המפתח הפומבי שלהם.\n\n<small>אנא בקש מאנשי הקשר שלך לארגן OpenPGP.</small></string> - <string name="encrypted_message_received"><i>הודעה מוצפנת התקבלה. לחץ כדי לצפות ולפענח.</i></string> + <string name="encrypted_message_received"><i>הודעה מוצפנת תנקבלה. גע כדי לפענח צופן.</i></string> + <string name="pref_general">כללי</string> <string name="pref_xmpp_resource">משאב XMPP</string> - <string name="pref_xmpp_resource_summary">השם שלקוח זה מזהה את עצמו עם</string> + <string name="pref_xmpp_resource_summary">השם שבעזרתו לקוח זה מזהה את עצמו</string> <string name="pref_accept_files">קבל קבצים</string> - <string name="pref_accept_files_size_summary">קבל אוטומטית קבצים קטנים יותר מאשר…</string> - <string name="pref_notification_settings">הגדרות התראה</string> + <string name="pref_accept_files_summary">קבל אוטומטית קבצים שגודלם קטן מ…</string> <string name="pref_notifications">התראות</string> <string name="pref_notifications_summary">תודיע כאשר הודעה חדשה מגיעה</string> <string name="pref_vibrate">הרטט</string> <string name="pref_vibrate_summary">הרטט גם כאשר הודעה חדשה מגיעה</string> <string name="pref_sound">צליל</string> - <string name="pref_sound_summary">נגן צלצול עם התראה</string> - <string name="pref_conference_notifications">התראות ועידה</string> - <string name="pref_conference_notifications_summary">תמיד תודיע כאשר הודעת ועידה חדשה מגיעה במקום רק כאשר מודגשת</string> - <string name="pref_notification_grace_period">משך ארכת התראה</string> - <string name="pref_notification_grace_period_summary">נטרל התראות לזמן קצר לאחר שהודעת פחם התקבלה</string> - <string name="pref_advanced_options">אפשרויות מתקדמות</string> + <string name="pref_sound_summary">נגן צלצול עם כל התראה</string> + <string name="pref_notification_grace_period">משך תקופת ארכה</string> + <string name="pref_notification_grace_period_summary">נטרל התראות לזמן קצר לאחר שהודעת Carbon Copy מתקבלת</string> <string name="pref_never_send_crash">לעולם אל תשלח דיווחי קריסה</string> <string name="pref_never_send_crash_summary">על ידי שליחת עקבות מחסנית אתה עוזר להתקדמות הפיתוח של Conversations</string> <string name="pref_confirm_messages">אשר הודעות</string> <string name="pref_confirm_messages_summary">אפשר לאיש קשר שלך לדעת מתי קיבלת וקראת הודעה</string> - <string name="pref_ui_options">אפשרויות ממשק משתמש</string> - <string name="openpgp_error">OpenKeychain דיווח שגיאה</string> + <string name="openpgp_error">אפליקציית OpenKeychain דיווחה על שגיאה</string> <string name="error_decrypting_file">שגיאת I/O פענוח קובץ</string> <string name="accept">קבל</string> <string name="error">אירעה שגיאה</string> @@ -113,33 +125,37 @@ <string name="receive_presence_updates">קבל עדכוני נוכחות</string> <string name="ask_for_presence_updates">בקש עדכוני נוכחות</string> <string name="attach_choose_picture">בחר תמונה</string> - <string name="attach_take_picture">קח תמונה</string> - <string name="preemptively_grant">הענק בקשת הרשמה מראש</string> - <string name="error_not_an_image_file">הקובץ שבחרת אינו תצלום</string> - <string name="error_compressing_image">שגיאה במהלך המרת קובץ תצלום</string> + <string name="attach_take_picture">צלם תמונה</string> + <string name="preemptively_grant">הענק מראש בקשת הרשמה</string> + <string name="error_not_an_image_file">הקובץ שבחרת אינו תמונה</string> + <string name="error_compressing_image">שגיאה במהלך המרת תמונה</string> <string name="error_file_not_found">קובץ לא נמצא</string> <string name="error_io_exception">שגיאת I/O כללית. אולי אזל לך נפח אחסון?</string> - <string name="error_security_exception_during_image_copy">האפליקציה בה השתמשת כדי לבחור את תצלום זה לא סיפקה לנו מספיק הרשאות כדי לקרוא את הקובץ.\n\n<small>השתמש במנהל קבצים אחר כדי לבחור תצלום</small></string> + <string name="error_security_exception_during_image_copy">האפליקציה בה השתמשת כדי לבחור תמונה זו לא סיפקה לנו מספיק הרשאות כדי לקרוא את הקובץ.\n\n<small>השתמש במנהל קבצים אחר כדי לבחור תצלום</small></string> <string name="account_status_unknown">לא ידוע</string> <string name="account_status_disabled">מנוטרל זמנית</string> <string name="account_status_online">מקוון</string> - <string name="account_status_connecting">כעת מתחבר\u2026</string> + <string name="account_status_connecting">מתחבר\u2026</string> <string name="account_status_offline">לא מקוון</string> <string name="account_status_unauthorized">לא מורשה</string> <string name="account_status_not_found">שרת לא נמצא</string> <string name="account_status_no_internet">אין חיבוריות</string> <string name="account_status_regis_fail">הרשמה נכשלה</string> - <string name="account_status_regis_conflict">שם משתמש כבר מצוי בשימוש</string> + <string name="account_status_regis_conflict">שם משתמש כבר בשימוש</string> <string name="account_status_regis_success">הרשמה הושלמה</string> - <string name="account_status_regis_not_sup">שרת לא תומך הרשמה</string> - <string name="encryption_choice_none">טקסט גלוי</string> + <string name="account_status_regis_not_sup">השרת לא תומך בהרשמת משתמשים חדשים</string> + <string name="account_status_security_error">שגיאת אבטחה</string> + <string name="account_status_incompatible_server">שרת לא מתאים</string> + <string name="encryption_choice_unencrypted">לא מוצפן</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">ערוך חשבון</string> <string name="mgmt_account_delete">מחק</string> <string name="mgmt_account_disable">נטרל זמנית</string> - <string name="mgmt_account_publish_avatar">פרסם אווטאר</string> - <string name="mgmt_account_enable">אפשר</string> + <string name="mgmt_account_publish_avatar">פרסם תמונת פרופיל</string> + <string name="mgmt_account_publish_pgp">פרסם מפתח ציבורי של OpenPGP</string> + <string name="mgmt_account_enable">הפעל חשבון</string> <string name="mgmt_account_are_you_sure">האם אתה בטוח?</string> <string name="mgmt_account_delete_confirm_text">אם אתה מוחק את חשבונך כל היסטוריית הדיון שלך תאבד</string> <string name="attach_record_voice">הקלט קול</string> @@ -150,34 +166,48 @@ <string name="password">סיסמה</string> <string name="confirm_password">אמת סיסמה</string> <string name="passwords_do_not_match">סיסמאות לא תואמות</string> - <string name="invalid_jid">זה אינו מזהה Jabber תקף</string> + <string name="invalid_jid">מזהה ה Jabber אינו תקין</string> <string name="error_out_of_memory">חסר זיכרון. תצלום גדול מדי</string> - <string name="add_phone_book_text">האם ברצונך להוסיף את %s אל רשימת קשר טלפונית?</string> <string name="contact_status_online">מקוון</string> <string name="contact_status_free_to_chat">חופשי לשיחה</string> <string name="contact_status_away">נעדר</string> - <string name="contact_status_extended_away">נעדר לזמן מה</string> - <string name="contact_status_do_not_disturb">אל תפריעו</string> + <string name="contact_status_extended_away">נעדר לזמן ממושך</string> + <string name="contact_status_do_not_disturb">נא לא להפריע</string> <string name="contact_status_offline">לא מקוון</string> <string name="muc_details_conference">ועידה</string> <string name="muc_details_other_members">חברים אחרים</string> - <string name="server_info_carbon_messages">הודעות פחם</string> - <string name="server_info_stream_management">ניהול זרם</string> + <string name="server_info_show_more">פרטי השרת</string> + <string name="server_info_mam">XEP-0313: MAM - היסטוריית שרת</string> + <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> + <string name="server_info_csi">XEP-0352: Client State Indication</string> + <string name="server_info_blocking">XEP-0191: Blocking Command - חסימת אנשי קשר</string> + <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> + <string name="server_info_stream_management">XEP-0198: Stream Management</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO) - תמונת פרופיל והצפנת OMEMO</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload - שליחת קבצים דרך HTTP</string> + <string name="server_info_available">זמין</string> + <string name="server_info_unavailable">לא זמין</string> <string name="missing_public_keys">הכרזות מפתח פומבי חסרות</string> <string name="last_seen_now">נראה לאחרונה ממש עכשיו</string> - <string name="last_seen_min">נראה לאחרונה לפני דקה 1</string> + <string name="last_seen_min">נראה לאחרונה לפני דקה</string> <string name="last_seen_mins">נראה לאחרונה לפני %d דקות</string> - <string name="last_seen_hour">נראה לאחרונה לפני שעה 1</string> - <string name="last_seen_hours">נראה לאחרונה לפני %d שעות ago</string> - <string name="last_seen_day">נראה לאחרונה לפני יום 1</string> + <string name="last_seen_hour">נראה לאחרונה לפני שעה</string> + <string name="last_seen_hours">נראה לאחרונה לפני %d שעות</string> + <string name="last_seen_day">נראה לאחרונה לפני יום אחד</string> <string name="last_seen_days">נראה לאחרונה לפני %d ימים</string> <string name="never_seen">לא נראה מעולם</string> - <string name="install_openkeychain">הודעה מוצפנת. אנא התקן OpenKeychain כדי לפענח.</string> + <string name="install_openkeychain">הודעה מוצפנת. אנא התקן את OpenKeychain כדי לפענח.</string> <string name="unknown_otr_fingerprint">טביעת אצבע OTR לא מוכרת</string> <string name="openpgp_messages_found">הודעות מוצפנות OpenPGP נמצאו</string> <string name="reception_failed">קבלה נכשלה</string> <string name="your_fingerprint">טביעת אצבע שלך</string> - <string name="otr_fingerprint">טביעת אצבע OTR</string> + <string name="otr_fingerprint">טביעת אצבע של OTR</string> + <string name="omemo_fingerprint">טביעת אצבע של OMEMO</string> + <string name="omemo_fingerprint_selected_message">טביעת אצבע OMEMO של ההודעה</string> + <string name="this_device_omemo_fingerprint">טביעת אצבע OMEMO שלי</string> + <string name="other_devices">מכשירים אחרים</string> + <string name="trust_omemo_fingerprints">סמוך על טביעות אצבע OMEMO</string> + <string name="done">בוצע</string> <string name="verify">אמת</string> <string name="decrypt">פענח</string> <string name="conferences">ועידות</string> @@ -186,6 +216,8 @@ <string name="join_conference">הצטרף לועידה</string> <string name="delete_contact">מחק איש קשר</string> <string name="view_contact_details">צפה בפרטי איש קשר</string> + <string name="block_contact">חסום איש קשר</string> + <string name="unblock_contact">בטל חסימת איש קשר</string> <string name="create">צור</string> <string name="contact_already_exists">איש קשר כבר קיים</string> <string name="join">הצטרף</string> @@ -201,15 +233,267 @@ <string name="contact_added_you">איש קשר הוסיף אותך אל רשימת קשר</string> <string name="add_back">הוסף בחזרה</string> <string name="contact_has_read_up_to_this_point">%s קרא עד לנקודה זו</string> - <string name="touch_to_choose_picture">לחץ על אווטאר כדי לבחור תמונה מתוך גלריה</string> - <string name="publish_avatar_explanation">לתשומת לבך: כל מי אשר רשום לעדכוני נוכחות שלך יורשה לראות את תמונה זו.</string> - <string name="publishing">כעת מפרסם…</string> - <string name="error_publish_avatar_server_reject">השרת פסל פרסום</string> - <string name="error_publish_avatar_converting">משהו השתבש במהלך המרת תמונה</string> - <string name="error_saving_avatar">לא היה מסוגל לשמור אווטאר אל כונן</string> - <string name="or_long_press_for_default">(או לחיצה ארוכה כדי להחזיר לשגרה)</string> - <string name="error_publish_avatar_no_server_support">שרתך לא תומך בפרסום של אווטארים</string> + <string name="publish">פרסם</string> + <string name="touch_to_choose_picture">לחץ על תמות הפרופיל כדי לבחור תמונה מתוך הגלריה</string> + <string name="publish_avatar_explanation">לתשומת לבך: כל מי שרשום לעדכוני נוכחות שלך יורשה לראות את תמונה זו.</string> + <string name="publishing">מעלה…</string> + <string name="error_publish_avatar_server_reject">השרת דחה את ההעלאה</string> + <string name="error_publish_avatar_converting">משהו השתבש במהלך המרת התמונה</string> + <string name="error_saving_avatar">שגיאה בעת שמירת תמונה לזיכרון</string> + <string name="or_long_press_for_default">(או לחיצה ארוכה כדי להחזיר לברירת מחדל)</string> + <string name="error_publish_avatar_no_server_support">השרת לא תומך בפרסום של אווטארים</string> <string name="private_message">בפרטי</string> <string name="private_message_to">בפרטי אל %s</string> <string name="send_private_message_to">שלח הודעה פרטית אל %s</string> + <string name="connect">התחבר</string> + <string name="account_already_exists">חשבון זה כבר קיים</string> + <string name="next">הבא</string> + <string name="additional_information">מידע נוסף</string> + <string name="skip">דלג</string> + <string name="disable_notifications">השבת התראות</string> + <string name="disable_notifications_for_this_conversation">השבת התראות עבור השיחה הנוכחית</string> + <string name="enable">הפעל</string> + <string name="conference_requires_password">ועידה זאת דורשת סיסמא</string> + <string name="enter_password">הכנס סיסמא</string> + <string name="missing_presence_updates">עדכוני נוסחות חסרים מאיש הקשר</string> + <string name="request_presence_updates">נא לבקש עדכוני נוכחות מאיש הקשר קודם לכך<small>פעולה זו תשמש כדי לקבוע את אקפליקציה שאיש הקשר משתמש בה</small></string> + <string name="request_now">בקש/י כעת</string> + <string name="delete_fingerprint">מחק טביעת אצבע</string> + <string name="sure_delete_fingerprint">האם את/ה בטוח שברצונך למחוק טביעת אצבע זו?</string> + <string name="ignore">התעלם</string> + <string name="without_mutual_presence_updates"><b>אזהרה:</b>שליחה ללא הרשאות עדכוני נוכחות הדדיות עלולה לגרום לתוצאות בלתי צפויות.\n\n<small>השתמש בתפריט \"פרטי משתמש\" ואשר עדכוני נוכחות</small></string> + <string name="pref_force_encryption">אלץ הצפנת end-to-end</string> + <string name="pref_force_encryption_summary">תמיד שלח הודעות מוצפנות (חוץ מבועידות)</string> + <string name="pref_dont_save_encrypted">אל תשמור הודעות מוצפנות</string> + <string name="pref_dont_save_encrypted_summary">אזהרה: פעולה זו עלולה לגרום לאיבוד הודעות</string> + <string name="pref_expert_options_summary">נא להיזהר!</string> + <string name="title_activity_about">אודות Conversations</string> + <string name="pref_about_conversations_summary">אודות גרסה ורישיון </string> + <string name="title_pref_quiet_hours">שעות שקטות</string> + <string name="title_pref_quiet_hours_start_time">זמן התחלה</string> + <string name="title_pref_quiet_hours_end_time">זמן סיום</string> + <string name="title_pref_enable_quiet_hours">הפעל \"שעות שקטות\"</string> + <string name="pref_quiet_hours_summary">ההתראות יושבתו במהלך שעות שקטות</string> + <string name="pref_use_larger_font">הגדל גופן</string> + <string name="pref_use_larger_font_summary">הגדל גופן בכל האפליקציה</string> + <string name="pref_use_send_button_to_indicate_status">לחצן השליחה מעיד על הסטטוס</string> + <string name="pref_use_indicate_received">בקש אימות הגעת הודעות</string> + <string name="pref_use_indicate_received_summary">יופיע סימן \"וי\" ליד כל הודעה שהתקבלה על ידי איש הקשר, במידה והפעולה נתמכת על ידי איש הקשר.</string> + <string name="pref_use_send_button_to_indicate_status_summary">צבעו של לחצן שליחת ההודעות יעיד על סטטוס איש הקשר.</string> + <string name="pref_expert_options_other">אחר</string> + <string name="pref_conference_name">שם ועידה</string> + <string name="pref_conference_name_summary">השתמש בכותרת הועידה ולא ב- JID כשם לועידה</string> + <string name="toast_message_otr_fingerprint">טביעת אצבע של OTR הועתקה</string> + <string name="toast_message_omemo_fingerprint">טביעת אצבע של OMEMO הועתקה</string> + <string name="conference_banned">אתה חסום מלהיכנס לועידה זו</string> + <string name="conference_members_only">ועידה זו אינה ציבורית</string> + <string name="conference_kicked">גורשת מועידה זו</string> + <string name="using_account">משתמש בחשבון: %s</string> + <string name="not_connected_try_again">אינך מחובר. נסה שוב אחר כך</string> + <string name="check_x_filesize">בדוק גודל %s</string> + <string name="message_options">הגדרות הודעה</string> + <string name="copy_text">העתק טקסט</string> + <string name="copy_original_url">העתק קישור</string> + <string name="send_again">שלח שוב</string> + <string name="file_url">קישור קובץ</string> + <string name="message_text">טקסט הודעה</string> + <string name="url_copied_to_clipboard">הקישור הועתק</string> + <string name="message_copied_to_clipboard">ההודעה הועתקה</string> + <string name="image_transmission_failed">שליחת התמונה נכשלה</string> + <string name="scan_qr_code">סרוק ברקוד QR</string> + <string name="show_qr_code">הראה ברקוד QR</string> + <string name="show_block_list">הראה רשימת חסומים</string> + <string name="account_details">פרטי חשבון</string> + <string name="verify_otr">אמת OTR</string> + <string name="remote_fingerprint">טביעת אצבע מרוחקת</string> + <string name="scan">סרוק</string> + <string name="smp">Socialist Millionaire Protocol</string> + <string name="shared_secret_hint">שאלה או רמז</string> + <string name="shared_secret_secret">סוד משותף</string> + <string name="confirm">אמת</string> + <string name="in_progress">הפעולה מתבצעת</string> + <string name="respond">השב</string> + <string name="failed">נכשל</string> + <string name="secrets_do_not_match">הסודות אינם תואמים</string> + <string name="try_again">נסה שוב</string> + <string name="finish">סיים</string> + <string name="verified">אומת בהצלחה!</string> + <string name="smp_requested">איש הקשר ביקש אימות SMP</string> + <string name="no_otr_session_found">לא נמצא OTR Session תקין</string> + <string name="pref_keep_foreground_service">השאר שירות ב Foreground</string> + <string name="pref_keep_foreground_service_summary">מונע ממערכת ההפעלה לנתק את החיבור לשרת</string> + <string name="pref_export_logs">ייצא Logs</string> + <string name="pref_export_logs_summary">יצוא logs לכרטיס הזיכרון</string> + <string name="notification_export_logs_title">מייצא logs לכרטיס זיכרון</string> + <string name="choose_file">בחר קובץ</string> + <string name="receiving_x_file">מקבל %1$s ( הושלם %2$d%% )</string> + <string name="download_x_file">הורד %s</string> + <string name="file">קובץ</string> + <string name="open_x_file">פתח %s</string> + <string name="sending_file">שולח ( %1$d%% הושלם )</string> + <string name="preparing_file">מכין קובץ לשליחה</string> + <string name="x_file_offered_for_download">הקובץ %s הוצע לאיש הקשר</string> + <string name="cancel_transmission">בטל שליחה</string> + <string name="file_transmission_failed">השליחה נכשלה</string> + <string name="file_deleted">הקובץ נמחק</string> + <string name="no_application_found_to_open_file">אין אפליקציה מתאימה לפתיחת הקובץ</string> + <string name="could_not_verify_fingerprint">אימות טביעת האצבע נכשל</string> + <string name="manually_verify">אימות ידני</string> + <string name="are_you_sure_verify_fingerprint">האם אתה בטוח שברצונך לאמת טביעת אצבע OTR זו?</string> + <string name="pref_show_dynamic_tags">הראה תגים דומיים</string> + <string name="pref_show_dynamic_tags_summary">הראה תגי read-only מתחת לאנשי הקשר</string> + <string name="enable_notifications">אפשר התראות</string> + <string name="conference_with">צור ועידה עם...</string> + <string name="no_conference_server_found">לא נמצא שרת ועידות</string> + <string name="conference_creation_failed">יצירת הועידה נכשלה!</string> + <string name="conference_created">הועידה נוצרה!</string> + <string name="secret_accepted">הסוד התקבל!</string> + <string name="reset">איפוס</string> + <string name="account_image_description">תמונת פרופיל</string> + <string name="copy_otr_clipboard_description">העתק טביעת אצבע OTR</string> + <string name="copy_omemo_clipboard_description">העתק טביעת אצבע OMEMO</string> + <string name="regenerate_omemo_key">צור מפתח OMEMO חדש</string> + <string name="wipe_omemo_pep">מחק מכשרים אחרים מ- PEP</string> + <string name="clear_other_devices">נקה מכשירים</string> + <string name="clear_other_devices_desc">האם אתה בטוח שברצונך לנקות את כל המכשירים מהכרזת ה OMEMO? בפעם הבאה שהמכשירים יתחברו, הם יכריזו שוב על עצמם, אך הם עלולים לאבד הודעות עד להכרזה זו.</string> + <string name="purge_key">מחק מפתח לצמיתות</string> + <string name="purge_key_desc_part1">האם אתה בטוח שברצונך למחוק מפתח זה לצמיתות?</string> + <string name="purge_key_desc_part2">הוא ייחשב ללא תקין לצמיתות, ולעולם לא תצליח להשתמש בו שוב.</string> + <string name="error_no_keys_to_trust">אין מפתחות שימושיים עבור איש קשר זה. אם מחקת אחד מהמפתחות שלהם, יהיה עליהם ליצר מפתח חדש.</string> + <string name="error_trustkeys_title">שגיאה</string> + <string name="fetching_history_from_server">הורדת היסטוריה מהשרת</string> + <string name="no_more_history_on_server">אין עוד היסטוריה בשרת</string> + <string name="updating">מעדכן...</string> + <string name="password_changed">הסיסמה שונתה!</string> + <string name="could_not_change_password">שינוי הסיסמה נכשל</string> + <string name="otr_session_not_started">שלח הודעה בכדי להתחיל בהצפנה</string> + <string name="ask_question">שאל שאלה</string> + <string name="smp_explain_question">במידה ויש לך ולאיש הקשר שלך סוד שרק שניכם יודעים (כמו למשל בדיחה פנימית, או מה אכלתם בפעם האחרונה שנפגשתם), ניתן להשתמש בסוד הנ\"ל על מנת לאמת את מפתחות ההצפנה של אחד את השני.\n\nאיש קשר יספק שאלה או רמז, ואיש הקשר השני יספק תשובה. המלל הוא case-sensitive.</string> + <string name="smp_explain_answer">איש הקשר רוצה לאמת את הזהות שלך על ידי סוד משותף. איש הקשר סיפק את הרמז הבא, עליך להזין סוד משותף.</string> + <string name="shared_secret_hint_should_not_be_empty">הרמז לא יכול להיות ריק</string> + <string name="shared_secret_can_not_be_empty">הסוד המשותף לא יכול להיות ריק</string> + <string name="manual_verification_explanation">יש להשוות בזהירות את הטביעת אצבע שמוצגת פה לבין זאת שמוצגת אצל החבר שלך.\nניתן לעשות זאת על ידי שימוש בכל צורת תקשורת שאתה סומך עליה, כמו שיחת טלפון או אימייל מוצפן.</string> + <string name="change_password">שינוי סיסמה</string> + <string name="current_password">סיסמה נוכחית</string> + <string name="new_password">סיסמה חדשה</string> + <string name="password_should_not_be_empty">הסיסמה לא יכולה להיות ריקה</string> + <string name="enable_all_accounts">הפעל את כל החשבונות</string> + <string name="disable_all_accounts">נטרל את כל החשבונות</string> + <string name="perform_action_with">בצע פעולה באמצעות</string> + <string name="no_affiliation">אין שיוך</string> + <string name="no_role">אין תפקיד</string> + <string name="member">חבר בקבוצה</string> + <string name="advanced_mode">מצב מתקדם</string> + <string name="grant_membership">הענק חברות בקבוצה</string> + <string name="remove_membership">בטל חברות בקבוצה</string> + <string name="grant_admin_privileges">הענק הרשאות מנהל</string> + <string name="remove_admin_privileges">שלול הרשאות מנהל</string> + <string name="remove_from_room">סלק מהועידה</string> + <string name="could_not_change_affiliation">לא ניתן לשנות את השיוך של %s</string> + <string name="ban_from_conference">סלק וחסום כניסה לועידה</string> + <string name="removing_from_public_conference">הינך מנסה למחוק את %s מועידה ציבורית. הדרך היחידה לעשות זאת היא חסימה לצמיתות.</string> + <string name="ban_now">חסום עכשיו</string> + <string name="could_not_change_role">לא ניתן לשנות את התפקיד של %s</string> + <string name="public_conference">ועידה ציבורית</string> + <string name="private_conference">ועידה פרטית, לחברים בלבד</string> + <string name="conference_options">הגדרות ועידה</string> + <string name="members_only">פרטי, חברים בלבד</string> + <string name="non_anonymous">לא-אנונימי</string> + <string name="moderated">Moderated</string> + <string name="you_are_not_participating">אינך משתתף</string> + <string name="modified_conference_options">הגדרות הועידה השתנו בצלחה!</string> + <string name="could_not_modify_conference_options">שינוי הגדרות הועידה נכשל</string> + <string name="never">לעולם לא</string> + <string name="thirty_minutes">30 דקות</string> + <string name="one_hour">שעה אחת</string> + <string name="two_hours">2 שעות</string> + <string name="eight_hours">8 שעות</string> + <string name="until_further_notice">עד אחרית הימים</string> + <string name="pref_enter_is_send">לחצן Enter שולח את ההודעה</string> + <string name="pref_enter_is_send_summary">השתמש בלחצן ה-Enter כלחצן השליחה</string> + <string name="pref_display_enter_key">הראה את לחצן ה Enter</string> + <string name="pref_display_enter_key_summary">שנה את לחצן האימוג\'י ללחצן Enter</string> + <string name="audio">קול</string> + <string name="video">סרטון</string> + <string name="image">תמונה</string> + <string name="pdf_document">מסמך PDF</string> + <string name="apk">אפליקציית אנדרויד</string> + <string name="vcard">איש קשר</string> + <string name="received_x_file">התקבל %s</string> + <string name="disable_foreground_service">בטל שירות Foreground</string> + <string name="touch_to_open_conversations">לחץ כדי לפתוח את Conversations</string> + <string name="avatar_has_been_published">תמונת הפרופיל פורסמה!</string> + <string name="sending_x_file">שולח %s</string> + <string name="offering_x_file">מציע %s</string> + <string name="hide_offline">הסתר בלתי מקוונים</string> + <string name="disable_account">השבת חשבון</string> + <string name="contact_is_typing">%s מקליד/ה כעת…</string> + <string name="contact_has_stopped_typing">%s הפסיק/ה להקליד</string> + <string name="pref_chat_states">התראות הקלדה</string> + <string name="pref_chat_states_summary">אפשר לאנשי הקשר שלך לדעת כאשר אתה מקליד הודעה חדשה</string> + <string name="send_location">שלח מיקום</string> + <string name="show_location">הראה מיקום</string> + <string name="no_application_found_to_display_location">לא נמצאה אפליקציית המראה את המיקום</string> + <string name="location">מיקום</string> + <string name="received_location">מיקום שהתקבל</string> + <string name="title_undo_swipe_out_conversation">השיחה נסגרה</string> + <string name="title_undo_swipe_out_muc">עזבת את הועידה</string> + <string name="pref_dont_trust_system_cas_title">אל תסמוך על ה- CAs של המערכת</string> + <string name="pref_dont_trust_system_cas_summary">כל החתימות הדיגטליות יצטרכו לעבור אימות ידני</string> + <string name="pref_remove_trusted_certificates_title">מחק חתימות דיגטליות</string> + <string name="pref_remove_trusted_certificates_summary">מחק חתימות דיגטליות שאומתו באופן ידני</string> + <string name="toast_no_trusted_certs">אין חתימות דיגטליות שאושרו ידנית</string> + <string name="dialog_manage_certs_title">מחק חתימות דיגטליות</string> + <string name="dialog_manage_certs_positivebutton">מחק פריטים שנבחרו</string> + <string name="dialog_manage_certs_negativebutton">ביטול</string> + <plurals name="toast_delete_certificates"> + <item quantity="one">%d חתימה נמחקה</item> + <item quantity="other">%d חתימות נמחקו</item> + </plurals> + <plurals name="select_contact"> + <item quantity="one">בחר איש קשר %d</item> + <item quantity="other">בחר %d אנשי קשר</item> + </plurals> + <string name="pref_quick_action_summary">החלף לחצן שליחה בפעולה מהירה</string> + <string name="pref_quick_action">פעולה מהירה</string> + <string name="none">כלום</string> + <string name="recently_used">לפי השימוש האחרון</string> + <string name="choose_quick_action">בחר פעולה מהירה</string> + <string name="search_for_contacts_or_groups">חפש אנשי קשר או קבוצות</string> + <string name="send_private_message">שלח הודעה פרטית</string> + <string name="user_has_left_conference">%s עזב את הועידה</string> + <string name="username">שם משתמש</string> + <string name="username_hint">שם משתמש</string> + <string name="invalid_username">שם משתמש זה אינו חוקי</string> + <string name="download_failed_server_not_found">ההורדה נכשלה: שרת לא נמצא</string> + <string name="download_failed_file_not_found">ההורדה נכשלה: הקובץ לא נמצא</string> + <string name="download_failed_could_not_connect">ההורדה נכשלה: נכשל ביצוע חיבור לשרת</string> + <string name="server_info_broken">לא עובד</string> + <string name="pref_away_when_screen_off">העבר למצב \"לא נמצא\" כאשר המסך כבוי</string> + <string name="pref_away_when_screen_off_summary">מעביר את המכשיר לסטטוס \"לא נמצא\" כאשר המסך כבוי</string> + <string name="pref_xa_on_silent_mode">העבר למצב \"לא זמין\" כאשר במצב שקט</string> + <string name="action_add_account_with_certificate">הוסף חשבון עם תעודה</string> + <string name="unable_to_parse_certificate">תקלה בפענוח תעודה</string> + <string name="authenticate_with_certificate">השאר ריק כדי להזדהות בלי תעודה</string> + <string name="captcha_ocr">טקסט Captcha</string> + <string name="captcha_required">דרוש טקסט Captcha</string> + <string name="captcha_hint">אנא הזן את הטקסט מתוך התמונה</string> + <string name="certificate_chain_is_not_trusted">שרשרת תעודה אינה מהימנה</string> + <string name="jid_does_not_match_certificate">אין התאמה בין מזהה Jabber לבין תעודה</string> + <string name="action_renew_certificate">חידוש תעודה</string> + <string name="error_fetching_omemo_key">שגיאה בתפיסת OMEMO!</string> + <string name="account_settings_hostname">שם מארח</string> + <string name="account_settings_port">פורט</string> + <string name="not_a_valid_port">זהו אינו מספר פורט תקין</string> + <string name="not_valid_hostname">זהו אינו שם מארח תקין</string> + <string name="connected_accounts">%1$d מתוך %2$d חשבונות מחוברים</string> + <plurals name="x_messages"> + <item quantity="one">הודעה %d</item> + <item quantity="other">%d הודעות</item> + </plurals> + <string name="shared_file_with_x">שתף קובץ בעזרת %s</string> + <string name="shared_image_with_x">שתף תמונה בעזרת %s</string> + <string name="no_storage_permission">Conversations זקוקה לגישה לאחסון חיצוני</string> + <string name="sync_with_contacts">סנכרן עם אנשי קשר</string> + <string name="sync_with_contacts_long">Conversations מבקשת להתאים את רשימת XMPP שלך עם אנשי הקשר שלך כדי להציג את השמות המלאים שלהם כולל אווטארים.\n\nConversations רק תקרא את אנשי הקשר שלך ותתאים אותם באופן מקומי מבלי להעלות אותם לשרת שלך.\n\nכעת אתה תתבקש להעניק הרשאה כדי לגשת להתקן שלך.</string> </resources> diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 35347734..57289f5a 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">安全に会話</string> <string name="action_add_account">アカウントを追加</string> <string name="action_edit_contact">名前の編集</string> - <string name="action_add_phone_book">電話帳に追加</string> + <string name="action_add_phone_book">アドレス帳に追加</string> <string name="action_delete_contact">名簿から削除</string> <string name="action_block_contact">連絡先をブロック</string> <string name="action_unblock_contact">連絡先のブロックを解除</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d 分前</string> <string name="unread_conversations">未読の会話</string> <string name="sending">送信中…</string> - <string name="encrypted_message">メッセージを復号しています。しばらくお待ちください…</string> + <string name="message_decrypting">メッセージを復号化しています。しばらくお待ちください…</string> + <string name="pgp_message">OpenPGP 暗号化メッセージ</string> <string name="nick_in_use">ニックネームは既に使用されています</string> <string name="admin">管理者</string> <string name="owner">オーナー</string> @@ -76,8 +77,10 @@ <string name="delete_messages">メッセージを削除</string> <string name="also_end_conversation">その後、この会話を終了</string> <string name="choose_presence">連絡する参加を選択</string> - <string name="send_plain_text_message">プレーンテキストを送信</string> + <string name="send_unencrypted_message">暗号化されていないメッセージを送信</string> <string name="send_otr_message">OTR 暗号化メッセージを送信</string> + <string name="send_omemo_message">OMEMO 暗号化メッセージを送信</string> + <string name="send_omemo_x509_message">v\\OMEMO 暗号化メッセージを送信</string> <string name="send_pgp_message">OpenPGP 暗号化メッセージを送信</string> <string name="your_nick_has_been_changed">あなたのニックネームが変更されました</string> <string name="send_unencrypted">暗号化されていない送信</string> @@ -86,35 +89,34 @@ <string name="openkeychain_required_long">Conversations は <b>OpenKeychain</b> と呼ばれるサードパーティのアプリを利用して、メッセージの暗号化および復号化、そしてあなたの公開鍵を管理します。\n\nOpenKeychain は GPLv3 ライセンスの下で、F-Droid および Google Play から利用可能です。\n\n<small>(後で Conversations を再起動してください。)</small></string> <string name="restart">再起動</string> <string name="install">インストール</string> + <string name="openkeychain_not_installed">OpenKeychain をインストールしてください</string> <string name="offering">依頼中…</string> <string name="waiting">待機中…</string> <string name="no_pgp_key">OpenPGP の鍵はありません</string> <string name="contact_has_no_pgp_key">連絡先が公開鍵を通知しないため、Conversations はあなたのメッセージを暗号化することができません。\n\n<small>連絡先に OpenPGP をセットアップするように依頼してください。</small></string> <string name="no_pgp_keys">OpenPGP の鍵はありません</string> <string name="contacts_have_no_pgp_keys">連絡先が公開鍵を通知しないため、Conversations はあなたのメッセージを暗号化することができません。\n\n<small>連絡先に OpenPGP をセットアップするように依頼してください。</small></string> - <string name="encrypted_message_received"><i>暗号化されたメッセージを受信しました。タッチすると、表示および復号化します。</i></string> + <string name="encrypted_message_received"><i>暗号化されたメッセージを受信しました。タッチすると復号化します。</i></string> <string name="pref_general">全般</string> <string name="pref_xmpp_resource">XMPP リソース</string> <string name="pref_xmpp_resource_summary">自分自身を識別するこのクライアントの名前</string> <string name="pref_accept_files">ファイルを受取</string> <string name="pref_accept_files_summary">自動的に小さいファイルを受取…</string> - <string name="pref_notification_settings">通知設定</string> + <string name="pref_notification_settings">通知</string> <string name="pref_notifications">通知</string> <string name="pref_notifications_summary">新しいメッセージが到着したときに通知します</string> <string name="pref_vibrate">振動</string> <string name="pref_vibrate_summary">新しいメッセージが到着したときに振動もします</string> <string name="pref_sound">サウンド</string> <string name="pref_sound_summary">通知で着信音を再生します</string> - <string name="pref_conference_notifications">会議通知</string> - <string name="pref_conference_notifications_summary">新しい会議メッセージが到着したとき、ハイライト表示ではなく、常に通知します</string> <string name="pref_notification_grace_period">通知猶予期間</string> <string name="pref_notification_grace_period_summary">カーボンコピーを受信した後、短時間、通知を無効にします</string> - <string name="pref_advanced_options">詳細オプション</string> + <string name="pref_advanced_options">詳細</string> <string name="pref_never_send_crash">クラッシュレポートを送信しない</string> <string name="pref_never_send_crash_summary">スタックトレースを送信することで、あなたは Conversations の継続的な開発を支援しています</string> <string name="pref_confirm_messages">メッセージの確認</string> <string name="pref_confirm_messages_summary">あなたがメッセージを受け取って読んだことを、連絡先に知らせます</string> - <string name="pref_ui_options">UI オプション</string> + <string name="pref_ui_options">UI</string> <string name="openpgp_error">OpenKeychain がエラーを報告しました</string> <string name="error_decrypting_file">ファイルの復号化中に I/O エラー</string> <string name="accept">受付</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">サーバーが登録をサポートしていません</string> <string name="account_status_security_error">セキュリティ エラー</string> <string name="account_status_incompatible_server">互換性のないサーバー</string> - <string name="encryption_choice_none">プレーンテキスト</string> + <string name="encryption_choice_unencrypted">暗号化されていない</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">アカウントの編集</string> <string name="mgmt_account_delete">アカウントを削除</string> <string name="mgmt_account_disable">一時的に無効にする</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">パスワードが一致しません</string> <string name="invalid_jid">これは有効な Jabber ID ではありません</string> <string name="error_out_of_memory">メモリ不足です。画像が大きすぎます</string> - <string name="add_phone_book_text">電話の連絡先リストに %s を追加しますか?</string> + <string name="add_phone_book_text">%s をお使いのアドレス帳に追加しますか?</string> <string name="contact_status_online">オンライン</string> <string name="contact_status_free_to_chat">自由にチャットできます</string> <string name="contact_status_away">離席中</string> @@ -186,7 +189,9 @@ <string name="server_info_blocking">XEP-0191: ブロッキング コマンド</string> <string name="server_info_roster_version">XEP-0237: 名簿バージョニング</string> <string name="server_info_stream_management">XEP-0198: ストリーム管理</string> - <string name="server_info_pep">XEP-0163: 個人イベントプロトコル (アバター)</string> + <string name="server_info_pep">XEP-0163: PEP (アバター / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP ファイルアップロード</string> + <string name="server_info_push">XEP-0357: プッシュ</string> <string name="server_info_available">利用可能</string> <string name="server_info_unavailable">利用不可</string> <string name="missing_public_keys">公開鍵の通知がありません</string> @@ -204,17 +209,28 @@ <string name="reception_failed">受信に失敗しました</string> <string name="your_fingerprint">あなたのフィンガープリント</string> <string name="otr_fingerprint">OTR フィンガープリント</string> + <string name="omemo_fingerprint">OMEMO フィンガープリント</string> + <string name="omemo_fingerprint_x509">v\\OMEMO フィンガープリント</string> + <string name="omemo_fingerprint_selected_message">メッセージの OMEMO フィンガープリント</string> + <string name="omemo_fingerprint_x509_selected_message">メッセージの v\\OMEMO フィンガープリント</string> + <string name="this_device_omemo_fingerprint">自分の OMEMO フィンガープリント</string> + <string name="other_devices">他のデバイス</string> + <string name="trust_omemo_fingerprints">OMEMO フィンガープリントを信頼</string> + <string name="fetching_keys">鍵の取得中…</string> + <string name="done">完了</string> <string name="verify">検証</string> <string name="decrypt">復号化</string> <string name="conferences">会議</string> <string name="search">検索</string> <string name="create_contact">連絡先を作成</string> + <string name="enter_contact">連絡先を入力</string> <string name="join_conference">会議に参加</string> <string name="delete_contact">連絡先を削除</string> <string name="view_contact_details">連絡先の詳細を表示</string> <string name="block_contact">連絡先をブロック</string> <string name="unblock_contact">連絡先のブロックを解除</string> <string name="create">作成</string> + <string name="select">選択</string> <string name="contact_already_exists">連絡先はすでに存在します</string> <string name="join">参加</string> <string name="conference_address">会議アドレス</string> @@ -225,6 +241,7 @@ <string name="you">あなた</string> <string name="action_edit_subject">会議の件名を編集</string> <string name="conference_not_found">会議が見つかりません</string> + <string name="conference_unknown_error">不明なエラーを受け取りました</string> <string name="leave">退出</string> <string name="contact_added_you">連絡先があなたを連絡先リストに追加しました</string> <string name="add_back">戻りを追加</string> @@ -249,7 +266,6 @@ <string name="skip">スキップ</string> <string name="disable_notifications">通知を無効にする</string> <string name="disable_notifications_for_this_conversation">この会話の通知を無効にします</string> - <string name="notifications_disabled">通知を無効にしました</string> <string name="enable">有効</string> <string name="conference_requires_password">会議はパスワードが必要です</string> <string name="enter_password">パスワードを入力してください</string> @@ -260,12 +276,14 @@ <string name="sure_delete_fingerprint">このフィンガープリントを削除してもよろしいですか?</string> <string name="ignore">無視</string> <string name="without_mutual_presence_updates"><b>警告:</b> 相互の参加アップデートなしにこれを送信すると、予期しない問題が発生する可能性があります。\n\n<small>あなたの参加サブスクリプションを検証するために、連絡先の詳細に移動します。</small></string> - <string name="pref_encryption_settings">暗号化設定</string> + <string name="pref_security_settings">セキュリティ</string> <string name="pref_force_encryption">強制的にエンドツーエンド暗号化を使用する</string> <string name="pref_force_encryption_summary">常に暗号化されたメッセージを送信します (会議を除く)</string> + <string name="pref_allow_message_correction">メッセージの修正を許可</string> + <string name="pref_allow_message_correction_summary">連絡先が、遡及的に自分のメッセージを編集することを許可します</string> <string name="pref_dont_save_encrypted">暗号化されたメッセージを保存しない</string> <string name="pref_dont_save_encrypted_summary">警告: これはメッセージの損失につながる可能性があります</string> - <string name="pref_expert_options">上級者オプション</string> + <string name="pref_expert_options">エキスパート設定</string> <string name="pref_expert_options_summary">ご利用は注意してください</string> <string name="title_activity_about">Conversations について</string> <string name="pref_about_conversations_summary">ビルドおよびライセンス情報</string> @@ -283,7 +301,10 @@ <string name="pref_expert_options_other">その他</string> <string name="pref_conference_name">会議名</string> <string name="pref_conference_name_summary">会議を識別するために JID の代わりにルームのテーマを使用します</string> + <string name="pref_autojoin">自動的に会議に参加</string> + <string name="pref_autojoin_summary">会議ブックマークの自動参加フラグを尊重します</string> <string name="toast_message_otr_fingerprint">OTR フィンガープリントをクリップボードにコピーしました!</string> + <string name="toast_message_omemo_fingerprint">OMEMO フィンガープリントをクリップボードにコピーしました!</string> <string name="conference_banned">あなたはこの会議から禁止されています</string> <string name="conference_members_only">この会議はメンバーのみです</string> <string name="conference_kicked">あなたはこの会議からキックされました</string> @@ -307,7 +328,6 @@ <string name="verify_otr">OTR を検証</string> <string name="remote_fingerprint">リモート フィンガープリント</string> <string name="scan">スキャン</string> - <string name="or_touch_phones">(または電話をタッチ)</string> <string name="smp">ソーシャリスト ミリオネア プロトコル</string> <string name="shared_secret_hint">ヒントまたは質問</string> <string name="shared_secret_secret">共有の秘密</string> @@ -324,6 +344,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">サービスをフォアグラウンドに保持</string> <string name="pref_keep_foreground_service_summary">オペレーティングシステムが接続を切断するのを防止します</string> + <string name="pref_export_logs">ログをエクスポート</string> + <string name="pref_export_logs_summary">ログを SD カードに書き込みます</string> + <string name="notification_export_logs_title">ログを SD カードに書き込み中</string> <string name="choose_file">ファイルの選択</string> <string name="receiving_x_file">%1$s 受信中 (%2$d%% 完了)</string> <string name="download_x_file">%s のダウンロード</string> @@ -349,7 +372,18 @@ <string name="secret_accepted">秘密を受取ました!</string> <string name="reset">リセット</string> <string name="account_image_description">アカウント アバター</string> - <string name="copy_otr_clipboard_description">OTR フィンガープリントをクリップボードにコピーしました</string> + <string name="copy_otr_clipboard_description">OTR フィンガープリントをクリップボードにコピー</string> + <string name="copy_omemo_clipboard_description">OMEMO フィンガープリントをクリップボードにコピー</string> + <string name="regenerate_omemo_key">OMEMO キーを再生成</string> + <string name="wipe_omemo_pep">PEP から他のデバイスを消去</string> + <string name="clear_other_devices">デバイスをクリア</string> + <string name="clear_other_devices_desc">OMEMO のアナウンスから他のすべてのデバイスをクリアしてもよろしいですか? お使いのデバイスが次回接続したとき、それらは自分自身を再アナウンスしますが、それらはその間に送信されたメッセージを受信できない場合があります。</string> + <string name="purge_key">鍵を消去</string> + <string name="purge_key_desc_part1">この鍵を消去してもよろしいですか?</string> + <string name="purge_key_desc_part2">これは不可逆的に侵害とみなされ、あなたは再びそのセッションを構築することはできません。</string> + <string name="error_no_keys_to_trust_server_error">この連絡先で利用可能な鍵がありません。\nサーバーから新しい鍵の取得に失敗しました。おそらく、お使いの連絡先サーバーに何か問題があります。</string> + <string name="error_no_keys_to_trust">この連絡先で利用可能な鍵はありません。あなたがその鍵を消去している場合は、新しいものを生成してもらう必要があります。</string> + <string name="error_trustkeys_title">エラー</string> <string name="fetching_history_from_server">サーバーから履歴を取得中</string> <string name="no_more_history_on_server">サーバーにこれ以上履歴はありません</string> <string name="updating">アップデート中…</string> @@ -387,8 +421,10 @@ <string name="public_conference">公開アクセス可能な会議</string> <string name="private_conference">プライベート、メンバーのみの会議</string> <string name="conference_options">会議オプション</string> - <string name="members_only">プライベート (メンバーのみ)</string> + <string name="members_only">プライベート、メンバーのみ</string> <string name="non_anonymous">匿名でない</string> + <string name="moderated">司会</string> + <string name="you_are_not_participating">あなたは参加していません</string> <string name="modified_conference_options">会議オプションを変更しました!</string> <string name="could_not_modify_conference_options">会議オプションを変更できません</string> <string name="never">なし</string> @@ -397,7 +433,7 @@ <string name="two_hours">2 時間</string> <string name="eight_hours">8 時間</string> <string name="until_further_notice">通知があるまで</string> - <string name="pref_input_options">入力オプション</string> + <string name="pref_input_options">入力</string> <string name="pref_enter_is_send">Enter は送信</string> <string name="pref_enter_is_send_summary">Enter キーをメッセージの送信に使用します</string> <string name="pref_display_enter_key">Enter キーを表示</string> @@ -416,7 +452,7 @@ <string name="offering_x_file">%s の依頼中</string> <string name="hide_offline">オフラインを非表示にする</string> <string name="disable_account">アカウントを無効にする</string> - <string name="contact_is_typing">%s は入力中...</string> + <string name="contact_is_typing">%s は入力中…</string> <string name="contact_has_stopped_typing">%s は入力を停止しました</string> <string name="pref_chat_states">入力中通知</string> <string name="pref_chat_states_summary">あなたが新しいメッセージを書いている時に、連絡先に知らせます</string> @@ -426,8 +462,7 @@ <string name="location">位置</string> <string name="received_location">位置を受信しました</string> <string name="title_undo_swipe_out_conversation">会話が閉じられました</string> - <string name="title_undo_swipe_out_muc">退出した会話</string> - <string name="pref_certificate_options">証明書オプション</string> + <string name="title_undo_swipe_out_muc">会議を退出しました</string> <string name="pref_dont_trust_system_cas_title">システムの CA を信頼しない</string> <string name="pref_dont_trust_system_cas_summary">すべての証明書を手動で承認する必要があります</string> <string name="pref_remove_trusted_certificates_title">証明書を削除</string> @@ -447,6 +482,74 @@ <string name="none">なし</string> <string name="recently_used">最近使用した</string> <string name="choose_quick_action">クイックアクションの選択</string> - <string name="file_not_found_on_remote_host">リモートサーバーにファイルが見つかりません</string> <string name="search_for_contacts_or_groups">連絡先またはグループの検索</string> + <string name="send_private_message">プライベートメッセージを送信</string> + <string name="user_has_left_conference">%s が会議を退出しました!</string> + <string name="username">ユーザー名</string> + <string name="username_hint">ユーザー名</string> + <string name="invalid_username">これは有効なユーザー名ではありません</string> + <string name="download_failed_server_not_found">ダウンロードに失敗しました: サーバーが見つかりません</string> + <string name="download_failed_file_not_found">ダウンロードに失敗しました: ファイルが見つかりません</string> + <string name="download_failed_could_not_connect">ダウンロードに失敗しました: ホストに接続できませんでした</string> + <string name="account_status_tor_unavailable">Tor ネットワークが利用できません</string> + <string name="server_info_broken">壊れています</string> + <string name="pref_presence_settings">参加</string> + <string name="pref_away_when_screen_off">画面がオフのときは離席</string> + <string name="pref_away_when_screen_off_summary">画面がオフになっているとき、リソースを離席としてマークします</string> + <string name="pref_xa_on_silent_mode">サイレントモード時は利用不可</string> + <string name="pref_xa_on_silent_mode_summary">デバイスがサイレントモードのとき、リソースが利用不可としてマークします</string> + <string name="pref_show_connection_options">拡張接続設定</string> + <string name="pref_show_connection_options_summary">アカウントを設定するときにホスト名とポートの設定を表示します</string> + <string name="hostname_example">xmpp.example.com</string> + <string name="action_add_account_with_certificate">アカウントに証明書を追加</string> + <string name="unable_to_parse_certificate">証明書を解析できません</string> + <string name="authenticate_with_certificate">空にすると、証明書で認証します</string> + <string name="mam_prefs">アーカイブの設定</string> + <string name="server_side_mam_prefs">サーバーサイドのアーカイブの設定</string> + <string name="fetching_mam_prefs">アーカイブの設定を取得しています。しばらくお待ちください…</string> + <string name="unable_to_fetch_mam_prefs">アーカイブの設定を取得できません</string> + <string name="captcha_ocr">キャプチャ テキスト</string> + <string name="captcha_required">キャプチャが必要です</string> + <string name="captcha_hint">画像からテキストを入力してください</string> + <string name="certificate_chain_is_not_trusted">証明書チェーンは信頼済ではありません</string> + <string name="jid_does_not_match_certificate">Jabber ID が証明書と一致しません</string> + <string name="action_renew_certificate">証明書を更新</string> + <string name="error_fetching_omemo_key">OMEMO 鍵の取得中にエラー!</string> + <string name="verified_omemo_key_with_certificate">OMEMO 鍵の取得中にエラー!</string> + <string name="device_does_not_support_certificates">お使いのデバイスはクライアント証明書の選択をサポートしていません!</string> + <string name="pref_connection_options">接続</string> + <string name="account_settings_hostname">ホスト名</string> + <string name="account_settings_port">ポート</string> + <string name="not_a_valid_port">これは有効なポート番号ではありません</string> + <string name="not_valid_hostname">これは有効なホスト名ではありません</string> + <string name="connected_accounts">%1$d / %2$d アカウントが接続しました</string> + <plurals name="x_messages"> + <item quantity="other">%d メッセージ</item> + </plurals> + <string name="shared_file_with_x">%s でファイルを共有</string> + <string name="shared_image_with_x">%s で画像を共有</string> + <string name="no_storage_permission">Conversations は外部ストレージにアクセスが必要です</string> + <string name="sync_with_contacts">連絡先と同期</string> + <string name="sync_with_contacts_long">Conversations はフルネームやアバターを表示するために、連絡先と XMPP 名簿と一致するようにしたいです。\n\nConversations は、サーバーにアップロードすることはなく、ローカルで連絡先を読んで一致させるだけです。\n\n今、連絡先へのアクセス許可を付与するように求められます。</string> + <string name="certificate_information">証明書情報</string> + <string name="certificate_subject">表題</string> + <string name="certificate_issuer">発行者</string> + <string name="certificate_cn">一般名称</string> + <string name="certificate_o">組織</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(利用不可)</string> + <string name="certificate_not_found">証明書がありません</string> + <string name="notify_on_all_messages">すべてのメッセージで通知</string> + <string name="notify_only_when_highlighted">ハイライトされたときにのみ通知</string> + <string name="notify_never">通知は無効</string> + <string name="notify_paused">通知は一時停止</string> + <string name="always">常に</string> + <string name="automatically">自動</string> + <string name="battery_optimizations_enabled">バッテリー最適化が有効</string> + <string name="battery_optimizations_enabled_explained">お使いのデバイスは、Conversations で、通知の遅延やメッセージの損失につながる可能性のある、いくつかの重いバッテリーの最適化を行っています。\nそれらを無効にすることをお勧めします。</string> + <string name="battery_optimizations_enabled_dialog">お使いのデバイスは、Conversations で通知の遅延やメッセージの損失につながる可能性のある、いくつかの重いバッテリーの最適化を行っています。\n\n今、それらを無効にするように求められます。</string> + <string name="disable">無効</string> + <string name="selection_too_large">選択した範囲が大きすぎます</string> + <string name="no_accounts">(アクティベートしたアカウントはありません)</string> + <string name="this_field_is_required">このフィールドは必須です</string> </resources> diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml index cdc3737a..ec730843 100644 --- a/src/main/res/values-ko/strings.xml +++ b/src/main/res/values-ko/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">안전한 대화 </string> <string name="action_add_account">계정 추가 </string> <string name="action_edit_contact">이름 편집 </string> - <string name="action_add_phone_book">주소록에 추가 </string> + <string name="action_add_phone_book">주소록에 추가</string> <string name="action_delete_contact">명단에서 삭제 </string> <string name="action_block_contact">연락처 </string> <string name="action_unblock_contact">연락처 차단 해제 </string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d 분 전 </string> <string name="unread_conversations">읽지 않은 대화 </string> <string name="sending">보내는중... </string> - <string name="encrypted_message">메세지 복호화중입니다. 기다리세요...</string> + <string name="message_decrypting">메세지 복호화중입니다. 기다리세요...</string> + <string name="pgp_message">OpenPGP로 암호화된 메세지</string> <string name="nick_in_use">사용중인 별명입니다 </string> <string name="admin">관리자 </string> <string name="owner">소유자 </string> @@ -76,8 +77,10 @@ <string name="delete_messages">메세지 삭제 </string> <string name="also_end_conversation">나중에 이 대화 끝내기 </string> <string name="choose_presence">연락할 프레즌스 선택 </string> - <string name="send_plain_text_message">평문 메세지 전송 </string> + <string name="send_unencrypted_message">암호화하지 않은 메세지 전송</string> <string name="send_otr_message">OTR 암호화된 메세지 전송 </string> + <string name="send_omemo_message">OMEMO로 암호화된 메세지</string> + <string name="send_omemo_x509_message">v\\OMEMO로 암호화된 메세지 전송</string> <string name="send_pgp_message">OpenPGP 암호화된 메세지 전송 </string> <string name="your_nick_has_been_changed">닉네임이 변경되었습니다 </string> <string name="send_unencrypted">암호화하지 않고 전송 </string> @@ -86,35 +89,31 @@ <string name="openkeychain_required_long">Conversations는 메세지를 암호화 및 복호화하고 공개 키를 관리하기 위해 OpenKeychain이라는 제 3자 앱을 활용합니다. OpenKeychain은 GPLv3 라이센스를 사용하며 F-Droid와 Google Play에서 구하실 수 있습니다. (이후 Conversations를 재시작하세요) </string> <string name="restart">재시작 </string> <string name="install">설치 </string> + <string name="openkeychain_not_installed">OpenKeychain을 설치하세요</string> <string name="offering">제공중... </string> <string name="waiting">대기중... </string> <string name="no_pgp_key">OpenPGP 키가 발견되지 않음 </string> <string name="contact_has_no_pgp_key">당신의 연락처가 그들의 공개 키를 선언하지 않고 있기 때문에 Conversations는 당신의 메세지를 암호화할 수 없습니다. OpenPGP를 설정하도록 당신의 연락처에게 물어보세요. </string> <string name="no_pgp_keys">OpenPGP 키가 발견되지 않음 </string> <string name="contacts_have_no_pgp_keys">당신의 연락처가 그들의 공개 키를 선언하지 않고 있기 때문에 Conversations는 당신의 메세지를 암호화할 수 없습니다. OpenPGP를 설정하도록 당신의 연락처에게 물어보세요. </string> - <string name="encrypted_message_received">암호화된 메세지 수신됨. 터치해서 복호화 및 열람하세요. </string> + <string name="encrypted_message_received"><i>암호화된 메세지가 도착했습니다. 터치하여 복호화하세요.</i></string> <string name="pref_general">일반 </string> <string name="pref_xmpp_resource">XMPP 자원 </string> <string name="pref_xmpp_resource_summary">이 클라이언트가 자신을 알아보는 이름</string> <string name="pref_accept_files">파일 수락 </string> <string name="pref_accept_files_summary">이 크기보다 작은 파일을 자동으로 수락 </string> - <string name="pref_notification_settings">알림 설정 </string> <string name="pref_notifications">알림 </string> <string name="pref_notifications_summary">새 메세지 도착시 알림 </string> <string name="pref_vibrate">진동 </string> <string name="pref_vibrate_summary">새 메세지 도착시 진동 </string> <string name="pref_sound">소리 </string> <string name="pref_sound_summary">알림과 동시에 벨소리 재생 </string> - <string name="pref_conference_notifications">회의 알림 </string> - <string name="pref_conference_notifications_summary">새 회의 메세지가 도착시, 강조됐을 때 뿐만 아니라 항상 알림 </string> <string name="pref_notification_grace_period">알림 유예 </string> <string name="pref_notification_grace_period_summary">Carbon Copy 수신 후에 잠시동안 알림 해제</string> - <string name="pref_advanced_options">추가 설정 </string> <string name="pref_never_send_crash">충돌 보고서 보내지 않음 </string> <string name="pref_never_send_crash_summary">Stack trace 정보를 보냄으로서 Conversations의 개발에 기여할 수 있습니다 </string> <string name="pref_confirm_messages">메세지 확인 </string> <string name="pref_confirm_messages_summary">메세지를 수신하고 읽었는지를 연락처에게 알려줌 </string> - <string name="pref_ui_options">사용자 환경 설정 </string> <string name="openpgp_error">OpenKeychain이 오류를 보고합니다 </string> <string name="error_decrypting_file">파일 복호화 입출력 오류 </string> <string name="accept">수락 </string> @@ -149,9 +148,10 @@ <string name="account_status_regis_not_sup">서버가 등록을 지원하지 않습니다</string> <string name="account_status_security_error">보안 오류 </string> <string name="account_status_incompatible_server">호환되지 않는 서버 </string> - <string name="encryption_choice_none">평문 </string> + <string name="encryption_choice_unencrypted">암호화되지 않음</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP </string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">계정 편집 </string> <string name="mgmt_account_delete">계정 삭제 </string> <string name="mgmt_account_disable">임시로 해제</string> @@ -170,7 +170,7 @@ <string name="passwords_do_not_match">암호가 일치하지 않습니다 </string> <string name="invalid_jid">올바른 Jabber ID가 아닙니다 </string> <string name="error_out_of_memory">메모리 부족. 이미지 용량이 너무 큽니다 </string> - <string name="add_phone_book_text">%s를 기기의 연락처 목록에 추가하시겠습니까? </string> + <string name="add_phone_book_text">주소록에 %s를 추가하시겠습니까?</string> <string name="contact_status_online">접속중 </string> <string name="contact_status_free_to_chat">대화 가능 </string> <string name="contact_status_away">자리 비움 </string> @@ -186,7 +186,8 @@ <string name="server_info_blocking">XEP-0191: Blocking Command </string> <string name="server_info_roster_version">XEP-0237: Roster Versioning </string> <string name="server_info_stream_management">XEP-0198: Stream Management </string> - <string name="server_info_pep">XEP-0163: PEP (Avatars) </string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP 파일 업로드</string> <string name="server_info_available">가능 </string> <string name="server_info_unavailable">불가 </string> <string name="missing_public_keys">공개 키 선언 누락 </string> @@ -199,22 +200,33 @@ <string name="last_seen_days">%d 일 전까지 접속했었음 </string> <string name="never_seen">접속한적 없음 </string> <string name="install_openkeychain">암호화된 메세지. 복호화하기 위해 OpenKeychain을 설치하세요. </string> - <string name="unknown_otr_fingerprint">알 수 없는 OTR 지문 </string> + <string name="unknown_otr_fingerprint">알 수 없는 OTR 핑거프린트</string> <string name="openpgp_messages_found">OpenPGP 암호화 메세지 발견 </string> <string name="reception_failed">접수 실패 </string> - <string name="your_fingerprint">당신의 지문 </string> - <string name="otr_fingerprint">OTR 지문 </string> + <string name="your_fingerprint">당신의 핑거프린트</string> + <string name="otr_fingerprint">OTR 핑거프린트</string> + <string name="omemo_fingerprint">OMEMO 핑거프린트</string> + <string name="omemo_fingerprint_x509">v\\OMEMO 핑거프린트</string> + <string name="omemo_fingerprint_selected_message">메세지의 OMEMO 핑거프린트</string> + <string name="omemo_fingerprint_x509_selected_message">메세지의 v\\OMEMO 핑거프린트</string> + <string name="this_device_omemo_fingerprint">OMEMO 핑거프린트 소유</string> + <string name="other_devices">다른 기기들</string> + <string name="trust_omemo_fingerprints">OMEMO 핑거프린트 신뢰</string> + <string name="fetching_keys">키 가져오는 중...</string> + <string name="done">완료</string> <string name="verify">검증 </string> <string name="decrypt">복호화 </string> <string name="conferences">회의 </string> <string name="search">검색 </string> <string name="create_contact">연락처 생성 </string> + <string name="enter_contact">연락처 입력</string> <string name="join_conference">회의 참석 </string> <string name="delete_contact">연락처 삭제 </string> <string name="view_contact_details">연락처 정보 보기 </string> <string name="block_contact">연락처 차단 </string> <string name="unblock_contact">연락처 차단 해제 </string> <string name="create">만들기 </string> + <string name="select">선택</string> <string name="contact_already_exists">이미 존재하는 연락처입니다 </string> <string name="join">참석 </string> <string name="conference_address">회의 주소 </string> @@ -249,23 +261,20 @@ <string name="skip">건너뛰기 </string> <string name="disable_notifications">알림 해제 </string> <string name="disable_notifications_for_this_conversation">이 대화의 알림 해제 </string> - <string name="notifications_disabled">알림이 해제되었습니다 </string> <string name="enable">사용 </string> <string name="conference_requires_password">회의에 암호가 필요합니다 </string> <string name="enter_password">암호 입력 </string> <string name="missing_presence_updates">연락처로부터 프레즌스 업데이트 찾을 수 없음 </string> <string name="request_presence_updates">먼저 연락처로부터 프레즌스 업데이트를 요청하세요. 이는 당신의 연락처가 어떤 클라이언트를 사용하는지 결정하는 데 사용됩니다. </string> <string name="request_now">지금 요청 </string> - <string name="delete_fingerprint">지문 삭제 </string> - <string name="sure_delete_fingerprint">이 지문을 삭제하시겠습니까? </string> + <string name="delete_fingerprint">핑거프린트 삭제 </string> + <string name="sure_delete_fingerprint">이 핑거프린트를 삭제하시겠습니까? </string> <string name="ignore">무시 </string> <string name="without_mutual_presence_updates">경고: 상호간의 프레즌스 업데이트 없이 이것을 보내면 예기치 못한 문제를 발생시킬 수 있습니다. 당신의 프레즌스 구독을 검증하기 위해 연락처 상세 정보로 가세요. </string> - <string name="pref_encryption_settings">암호화 설정 </string> <string name="pref_force_encryption">강제적인 종단간 암호화</string> <string name="pref_force_encryption_summary">언제나 암호화 메세지로 전송 (회의 제외) </string> <string name="pref_dont_save_encrypted">암호화된 메세지 저장하지 않음 </string> <string name="pref_dont_save_encrypted_summary">경고: 메세지가 손실될 수 있습니다 </string> - <string name="pref_expert_options">전문가 설정 </string> <string name="pref_expert_options_summary">설정시 주의하시기 바랍니다</string> <string name="title_activity_about">Conversations에 대해서 </string> <string name="pref_about_conversations_summary">빌드 및 라이센스 정보 </string> @@ -283,16 +292,20 @@ <string name="pref_expert_options_other">기타 </string> <string name="pref_conference_name">회의 이름 </string> <string name="pref_conference_name_summary">회의를 식별하기 위해 JID 대신 방 제목을 사용 </string> - <string name="toast_message_otr_fingerprint">OTR 지문이 클립보드에 복사되었습니다 </string> + <string name="toast_message_otr_fingerprint">OTR 핑거프린트가 클립보드에 복사되었습니다 </string> + <string name="toast_message_omemo_fingerprint">OMEMO 핑거프린트가 클립보드에 복사되었습니다</string> <string name="conference_banned">당신은 이 회의에서 금지되었습니다 </string> <string name="conference_members_only">이 회의는 멤버 전용입니다 </string> <string name="conference_kicked">당신은 이 회의에서 추방되었습니다 </string> <string name="using_account">using account %s</string> + <string name="checking_x">HTTP 호스트에서 %s 확인 중</string> <string name="not_connected_try_again">접속중이 아닙니다. 다시 시도하세요. </string> + <string name="check_x_filesize">%s 크기 확인</string> <string name="message_options">메세지 설정 </string> <string name="copy_text">텍스트 복사 </string> <string name="copy_original_url">원본 URL 복사 </string> <string name="send_again">다시 보내기 </string> + <string name="file_url">파일 URL</string> <string name="message_text">메세지 텍스트 </string> <string name="url_copied_to_clipboard">URL이 클립보드에 복사되었습니다 </string> <string name="message_copied_to_clipboard">메세지가 클립보드에 복사되었습니다 </string> @@ -302,9 +315,8 @@ <string name="show_block_list">차단 목록 보기 </string> <string name="account_details">계정 정보 </string> <string name="verify_otr">OTR 검증 </string> - <string name="remote_fingerprint">Remote Fingerprint</string> + <string name="remote_fingerprint">원격 핑거프린트</string> <string name="scan">스캔 </string> - <string name="or_touch_phones">(혹은 기기 터치) </string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">힌트 혹은 질문 </string> <string name="shared_secret_secret">공유된 비밀 </string> @@ -321,6 +333,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">포어그라운드에서 서비스 유지 </string> <string name="pref_keep_foreground_service_summary">운영체제가 접속을 해제하지 못하도록 예방합니다 </string> + <string name="pref_export_logs">내보내기 기록</string> + <string name="pref_export_logs_summary">기록을 SD 카드에 쓰기</string> + <string name="notification_export_logs_title">기록을 SD 카드에 쓰는 중</string> <string name="choose_file">파일 선택 </string> <string name="receiving_x_file">수신중 %1$s (%2$d%% 완료) </string> <string name="download_x_file">%s 다운로드 </string> @@ -333,9 +348,9 @@ <string name="file_transmission_failed">파일 전송 실패 </string> <string name="file_deleted">파일이 삭제되었습니다 </string> <string name="no_application_found_to_open_file">파일을 열기 위한 앱이 발견되지 않았습니다 </string> - <string name="could_not_verify_fingerprint">지문을 검증할 수 없습니다 </string> + <string name="could_not_verify_fingerprint">핑거프린트를 검증할 수 없습니다 </string> <string name="manually_verify">수동 검증 </string> - <string name="are_you_sure_verify_fingerprint">연락처의 OTR 지문을 검증하시겠습니까? </string> + <string name="are_you_sure_verify_fingerprint">연락처의 OTR 핑거프린트를 검증하시겠습니까? </string> <string name="pref_show_dynamic_tags">동적 태그 표시 </string> <string name="pref_show_dynamic_tags_summary">연락처 밑에 읽기 전용 태그 표시 </string> <string name="enable_notifications">알림 사용 </string> @@ -346,7 +361,18 @@ <string name="secret_accepted">비밀 접수됨 </string> <string name="reset">초기화 </string> <string name="account_image_description">계정 아바타 </string> - <string name="copy_otr_clipboard_description">OTR 지문을 클립보드에 복사 </string> + <string name="copy_otr_clipboard_description">OTR 핑거프린트를 클립보드에 복사 </string> + <string name="copy_omemo_clipboard_description">OMEMO 핑거프린트를 클립보드에 복사</string> + <string name="regenerate_omemo_key">OMEMO 키 다시 생성</string> + <string name="wipe_omemo_pep">PEP로부터 다른 기기들 제거</string> + <string name="clear_other_devices">기기 제거</string> + <string name="clear_other_devices_desc">OMEMO 선언으로부터 모든 기기를 지우시겠습니까? 다음에 기기들이 접속할 때, 기기들이 스스로 다시 선언하지만, 그 동안에 보내진 메세지는 받지 못할수도 있습니다.</string> + <string name="purge_key">키 제거</string> + <string name="purge_key_desc_part1">이 키를 제거하시겠습니까?</string> + <string name="purge_key_desc_part2">이것은 되돌릴 수 없으며 훼손된 것으로 간주됩니다. 이것으로는 다시는 세션을 만들 수 없게 됩니다.</string> + <string name="error_no_keys_to_trust_server_error">이 연락처에 사용할 수 있는 키가 없습니다. \n서버로부터 새로운 키를 가져올 수 없습니다. 아마 연락처의 서버에 오류가 있는 것 같습니다.</string> + <string name="error_no_keys_to_trust">이 연락처에 사용할 수 있는 키가 없습니다. 당신이 그들의 키 중에서 어떤 것이든 제거했다면, 그들이 새로운 키를 만들어야 합니다.</string> + <string name="error_trustkeys_title">오류</string> <string name="fetching_history_from_server">서버로부터 기록 가져오는중 </string> <string name="no_more_history_on_server">서버에 더이상 기록이 없습니다 </string> <string name="updating">업데이트중...</string> @@ -354,11 +380,11 @@ <string name="could_not_change_password">암호를 변경할 수 없습니다 </string> <string name="otr_session_not_started">암호화된 대화를 시작하기 위해 메세지 보내기 </string> <string name="ask_question">질문하기 </string> - <string name="smp_explain_question">만약 당신과 당신의 연락처가 다른 사람은 모르는 비밀을 공유하고 있다면, 그 비밀을 서로의 지문을 검증하는 데 사용할 수 있습니다. 대소문자가 구분된 대답을 할 연락처에게 힌트나 질문을 주세요. </string> - <string name="smp_explain_answer">당신의 연락처는 당신의 지문을 검증하고자 공유된 비밀을 확인하려고 합니다. 당신의 연락처는 그 비밀에 관한 다음과 같은 힌트 혹은 질문을 제공했습니다. </string> + <string name="smp_explain_question">만약 당신과 당신의 연락처가 다른 사람은 모르는 비밀을 공유하고 있다면, 그 비밀을 서로의 핑거프린트를 검증하는 데 사용할 수 있습니다. 대소문자가 구분된 대답을 할 연락처에게 힌트나 질문을 주세요. </string> + <string name="smp_explain_answer">당신의 연락처는 당신의 핑거프린트를 검증하고자 공유된 비밀을 확인하려고 합니다. 당신의 연락처는 그 비밀에 관한 다음과 같은 힌트 혹은 질문을 제공했습니다. </string> <string name="shared_secret_hint_should_not_be_empty">힌트를 반드시 입력해야 합니다 </string> <string name="shared_secret_can_not_be_empty">공유된 비밀을 반드시 입력해야 합니다 </string> - <string name="manual_verification_explanation">아래에 보이는 지문을 당신의 연락처의 지문과 세심하게 비교하세요. 당신은 암호화된 이메일이나 전화와 같은 믿을만한 통신수단으로 이것을 주고 받을 수 있습니다. </string> + <string name="manual_verification_explanation">아래에 보이는 핑거프린트를 당신의 연락처의 핑거프린트와 세심하게 비교하세요. 당신은 암호화된 이메일이나 전화와 같은 믿을만한 통신수단으로 이것을 주고 받을 수 있습니다. </string> <string name="change_password">암호 변경 </string> <string name="current_password">현재 암호 </string> <string name="new_password">새 암호 </string> @@ -384,8 +410,10 @@ <string name="public_conference">공개적으로 접근 가능한 회의 </string> <string name="private_conference">멤버 전용 사설 회의 </string> <string name="conference_options">회의 설정 </string> - <string name="members_only">사설 (멤버 전용) </string> + <string name="members_only">멤버 전용 (사설)</string> <string name="non_anonymous">익명 아님 </string> + <string name="moderated">중재됨</string> + <string name="you_are_not_participating">당신은 참여하고 있지 않습니다</string> <string name="modified_conference_options">회의 설정 변경됨 </string> <string name="could_not_modify_conference_options">회의 설정을 변경할 수 없습니다 </string> <string name="never">안함 </string> @@ -394,7 +422,6 @@ <string name="two_hours">2시간 </string> <string name="eight_hours">8시간 </string> <string name="until_further_notice">나중에 알릴때까지 </string> - <string name="pref_input_options">입력 설정 </string> <string name="pref_enter_is_send">엔터 키로 전송 </string> <string name="pref_enter_is_send_summary">엔터 키로 메세지를 보냅니다 </string> <string name="pref_display_enter_key">엔터 키 표시 </string> @@ -413,7 +440,7 @@ <string name="offering_x_file">%s 제공중 </string> <string name="hide_offline">오프라인 숨기기 </string> <string name="disable_account">계정 해제 </string> - <string name="contact_is_typing">%s 이(가) 입력중입니다... </string> + <string name="contact_is_typing">%s 이(가) 입력중입니다...</string> <string name="contact_has_stopped_typing">%s 이(가) 입력을 중단했습니다 </string> <string name="pref_chat_states">입력 알림 </string> <string name="pref_chat_states_summary">새 메세지를 작성할 때 이를 연락처에게 알립니다 </string> @@ -424,7 +451,6 @@ <string name="received_location">위치 수신 </string> <string name="title_undo_swipe_out_conversation">대화 끝남 </string> <string name="title_undo_swipe_out_muc">회의에서 나감 </string> - <string name="pref_certificate_options">인증 설정 </string> <string name="pref_dont_trust_system_cas_title">시스템 CA를 신뢰하지 않음 </string> <string name="pref_dont_trust_system_cas_summary">모든 인증서는 수동으로 승인되어야 함 </string> <string name="pref_remove_trusted_certificates_title">인증서 삭제 </string> @@ -444,4 +470,64 @@ <string name="none">없음</string> <string name="recently_used">최근 사용된 항목</string> <string name="choose_quick_action">빠른 동작 선택</string> + <string name="search_for_contacts_or_groups">연락처 또는 그룹 검색</string> + <string name="send_private_message">개인 메세지 전송</string> + <string name="user_has_left_conference">%s 이(가) 회의에서 나갔습니다.</string> + <string name="username">사용자 이름</string> + <string name="username_hint">사용자 이름</string> + <string name="invalid_username">이것은 올바른 사용자 이름이 아닙니다</string> + <string name="download_failed_server_not_found">다운로드 실패: 서버가 발견되지 않음</string> + <string name="download_failed_file_not_found">다운로드 실패: 파일이 발견되지 않음</string> + <string name="download_failed_could_not_connect">다운도륻 실패: 호스트에 접속할 수 없음</string> + <string name="pref_use_white_background">하얀색 배경 사용</string> + <string name="pref_use_white_background_summary">받은 메세지를 하얀색 배경에 검은색 글씨로 표시</string> + <string name="account_status_tor_unavailable">Tor 네트워크 사용할 수 없음</string> + <string name="server_info_broken">손상됨</string> + <string name="pref_away_when_screen_off">화면이 꺼져있을 경우 자리 비움으로 표시</string> + <string name="pref_away_when_screen_off_summary">화면이 꺼져있을 경우에 자리 비움으로 상태를 표시함</string> + <string name="pref_xa_on_silent_mode">음소거 모드에서는 사용할 수 없음</string> + <string name="pref_xa_on_silent_mode_summary">기기가 음소거 모드일때는 사용할 수 없음으로 상태를 표시함</string> + <string name="action_add_account_with_certificate">인증서가 있는 계정 추가</string> + <string name="unable_to_parse_certificate">인증서를 분석할 수 없음</string> + <string name="captcha_ocr">Captcha 텍스트</string> + <string name="captcha_required">Captcha가 필요함</string> + <string name="captcha_hint">이미지로부터 텍스트 입력</string> + <string name="certificate_chain_is_not_trusted">인증서 체인을 신뢰할 수 없습니다</string> + <string name="jid_does_not_match_certificate">Jabber ID가 인증서와 일치하지 않습니다</string> + <string name="action_renew_certificate">인증서 갱신</string> + <string name="error_fetching_omemo_key">OMEMO key를 가져오는 도중 오류가 발생했습니다</string> + <string name="verified_omemo_key_with_certificate">OMEMO 키와 인증서 검증됨</string> + <string name="device_does_not_support_certificates">기기가 선택된 클라이언트 인증서를 지원하지 않습니다</string> + <string name="account_settings_hostname">호스트 이름</string> + <string name="account_settings_port">포트</string> + <string name="not_a_valid_port">올바른 포트 번호가 아닙니다</string> + <string name="not_valid_hostname">올바른 호스트 이름이 아닙니다</string> + <string name="connected_accounts">%2$d 중 %1$d 계정이 연결되었습니다</string> + <plurals name="x_messages"> + <item quantity="other">%d 메세지</item> + </plurals> + <string name="shared_file_with_x">파일을 %s와 공유함</string> + <string name="shared_image_with_x">이미지를 %s와 공유함</string> + <string name="no_storage_permission">Conversations는 외부 저장소로의 접근을 필요로 합니다</string> + <string name="sync_with_contacts">연락처와 동기화</string> + <string name="sync_with_contacts_long">Conversations는 XMPP 명단과 연락처 명단을 대조시켜서 이름과 아바타를 표시하고자 합니다.\n\nConversations는 당신의 서버에 업로드하지 않고 기기 내부적으로만 연락처를 읽고 대조를 할 것입니다.\n\n이제 연락처에 접근할 권한을 당신에게 물어볼 것입니다.</string> + <string name="certificate_information">인증서 정보</string> + <string name="certificate_subject">제목</string> + <string name="certificate_issuer">발행자</string> + <string name="certificate_cn">이름</string> + <string name="certificate_o">단체</string> + <string name="certicate_info_not_available">(사용할 수 없음)</string> + <string name="certificate_not_found">인증서가 발견되지 않았습니다</string> + <string name="notify_on_all_messages">모든 메세지를 알림</string> + <string name="notify_only_when_highlighted">중요 표시를 했을 때만 알림</string> + <string name="notify_never">알림 해제됨</string> + <string name="notify_paused">알림 일시중지됨</string> + <string name="pref_picture_compression">사진 압축</string> + <string name="always">항상</string> + <string name="automatically">자동</string> + <string name="battery_optimizations_enabled">배터리 최적화 사용됨</string> + <string name="battery_optimizations_enabled_explained">당신의 기기는 메세지를 받지 못하게 되거나 알림을 지연시킬 수도 있는 고강도의 배터리 최적화를 Conversations에 하고 있습니다.\n배터리 최적화를 해제하는 것을 추천합니다.</string> + <string name="battery_optimizations_enabled_dialog">당신의 기기는 메세지를 받지 못하게 되거나 알림을 지연시킬 수도 있는 고강도의 배터리 최적화를 Conversations에 하고 있습니다.\n이것을 해제할 것인지 물어볼 것입니다.</string> + <string name="disable">해제</string> + <string name="selection_too_large">선택된 영역이 너무 큽니다</string> </resources> diff --git a/src/main/res/values-nb-rNO/strings.xml b/src/main/res/values-nb-rNO/strings.xml new file mode 100644 index 00000000..47d7334d --- /dev/null +++ b/src/main/res/values-nb-rNO/strings.xml @@ -0,0 +1,543 @@ +<?xml version='1.0' encoding='UTF-8'?> +<resources> + <string name="action_settings">Innstillinger</string> + <string name="action_add">Ny samtale</string> + <string name="action_accounts">Kontobehandling</string> + <string name="action_end_conversation">Avslutt denne samtalen</string> + <string name="action_contact_details">Kontaktdetaljer</string> + <string name="action_muc_details">Konferansedetaljer</string> + <string name="action_secure">Sikret samtale</string> + <string name="action_add_account">Legg til samtale</string> + <string name="action_edit_contact">Rediger navn</string> + <string name="action_add_phone_book">Legg til i kontaktliste</string> + <string name="action_delete_contact">Fjern fra kontaktliste</string> + <string name="action_block_contact">Blokker kontakt</string> + <string name="action_unblock_contact">Avblokker kontakt</string> + <string name="action_block_domain">Blokker domene</string> + <string name="action_unblock_domain">Avblokker domene</string> + <string name="title_activity_manage_accounts">Kontobehandling</string> + <string name="title_activity_settings">Innstillinger</string> + <string name="title_activity_conference_details">Konferansedetaljer</string> + <string name="title_activity_contact_details">Kontaktdetaljer</string> + <string name="title_activity_sharewith">Del med Conversation</string> + <string name="title_activity_start_conversation">Start samtale</string> + <string name="title_activity_choose_contact">Velg kontakt</string> + <string name="title_activity_block_list">Blokkeringsliste</string> + <string name="just_now">akkurat nå</string> + <string name="minute_ago">1 minutt siden</string> + <string name="minutes_ago">%d minutter siden</string> + <string name="unread_conversations">uleste samtaler</string> + <string name="sending">sender...</string> + <string name="message_decrypting">Dekrypterer melding mens du venter.</string> + <string name="pgp_message">OpenPGP-kryptert melding</string> + <string name="nick_in_use">Kallenavn allerede i bruk</string> + <string name="admin">Admin</string> + <string name="owner">Eier</string> + <string name="moderator">Moderator</string> + <string name="participant">Deltager</string> + <string name="visitor">Besøkende</string> + <string name="remove_contact_text">Bekreft fjerning av %s fra din kontaktliste. Samtalen med denne kontakten vil ikke bli fjernet.</string> + <string name="block_contact_text">Vil du forhindre %s fra å sende deg meldinger?</string> + <string name="unblock_contact_text">Ønsker du å avblokkere %s og tillate dem å sende deg meldinger?</string> + <string name="block_domain_text">Blokker alle kontakter fra %s?</string> + <string name="unblock_domain_text">Avblokker alle kontakter fra %s?</string> + <string name="contact_blocked">Kontakt blokkert</string> + <string name="remove_bookmark_text">Vil du fjerne %s som bokmerke? Samtalen det skriver seg fra vil ikke bli fjernet.</string> + <string name="register_account">Registrer ny konto på tjeneren</string> + <string name="change_password_on_server">Endre passord på tjeneren</string> + <string name="share_with">Del med...</string> + <string name="start_conversation">Start samtale</string> + <string name="invite_contact">Inviter kontakt</string> + <string name="contacts">Kontakter</string> + <string name="cancel">Avbryt</string> + <string name="set">Sett</string> + <string name="add">Legg til</string> + <string name="edit">Rediger</string> + <string name="delete">Slett</string> + <string name="block">Blokker</string> + <string name="unblock">Avblokker</string> + <string name="save">Lagre</string> + <string name="ok">OK</string> + <string name="crash_report_title">Conversations har kræsjet</string> + <string name="crash_report_message">Ved å sende inn stabelsporinger hjelper du den pågående utviklingen av Conversations\n<b>Advarsel:</b> Dette bruker din XMPP-konto til å sende stabelsporinger til utvikleren.</string> + <string name="send_now">Send nå</string> + <string name="send_never">Aldri spør igjen</string> + <string name="problem_connecting_to_account">Kunne ikke koble til konto</string> + <string name="problem_connecting_to_accounts">Kunne ikkekoble til flerforldige kontoer</string> + <string name="touch_to_fix">Trykk her for behandling av kontaktene dine</string> + <string name="attach_file">Legg til fil</string> + <string name="not_in_roster">Kontakten finnes ikke i din liste. Vil du legge den til?</string> + <string name="add_contact">Legg til kontakt</string> + <string name="send_failed">forsendelse feilet</string> + <string name="send_rejected">avslått</string> + <string name="preparing_image">Forbereder bilde for forsendelse</string> + <string name="action_clear_history">Tøm historikk</string> + <string name="clear_conversation_history">Tøm samtalehistorikk</string> + <string name="clear_histor_msg">Ønsker du å slette alle meldinger i denne samtalen?\n\n<b>Advarsel:</b> Dette har ingen innvirkning på meldinger lagret på andre enheter eller tjenere.</string> + <string name="delete_messages">Slett meldinger</string> + <string name="also_end_conversation">Avslutt denne samtalen etterpå</string> + <string name="choose_presence">Velg tilgjengelighetsoppdatering til kontakt</string> + <string name="send_unencrypted_message">Send ukryptert melding</string> + <string name="send_otr_message">Send OTR-kryptert melding</string> + <string name="send_omemo_message">Send OMEMO-kryptert melding</string> + <string name="send_omemo_x509_message">Send \\OMEMO-kryptert melding</string> + <string name="send_pgp_message">Send OpenPGP-kryptert melding</string> + <string name="your_nick_has_been_changed">Kallenavnet ditt har blitt endret</string> + <string name="send_unencrypted">Send ukryptert</string> + <string name="decryption_failed">Dekryptering feilet. Kanskje du ikke lenger har den rette private nøkkelen.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations nyttegjør seg av et tredjepartsprogram kalt <b>OpenKeychain</b> for å kryptere og dekryptere meldinger og behandle offentlige nøkler. \n\nOpenKeychain er lisensiert GPLv3 og er tilgjengelig på F-Droid og Google Play.\n\n<small>(Husk å gjøre en omstart av Conversations etterpå.)</small></string> + <string name="restart">Omstart</string> + <string name="install">Installer</string> + <string name="openkeychain_not_installed">Installer OpenKeychain</string> + <string name="offering">tilbyr...</string> + <string name="waiting">venter...</string> + <string name="no_pgp_key">Ingen OpenPGP-nøkkel funnet</string> + <string name="contact_has_no_pgp_key">Conversations kan ikke kryptere din melding fordi din kontakt ikke annonserer sin offentlige nøkkel.\n\n<small>Spør din kontakt om å sette opp OpenPGP.</small></string> + <string name="no_pgp_keys">Ingen OpenPGP-nøkler funnet</string> + <string name="contacts_have_no_pgp_keys">Conversations kan ikke kryptere din melding fordi dine kontakter ikke annonserer sine offentlige nøkkler.\n\n<small>Spør din kontakt om å sette opp OpenPGP.</small></string> + <string name="encrypted_message_received"><i>Kryptert melding mottatt. Trykk for å dekryptere.</i></string> + <string name="pref_general">Generelt</string> + <string name="pref_xmpp_resource">XMPP-ressurs</string> + <string name="pref_xmpp_resource_summary">Navnet denne klienten identifiserer seg med</string> + <string name="pref_accept_files">Godta filer</string> + <string name="pref_accept_files_summary">Automatisk godkjenning av filer mindre enn...</string> + <string name="pref_notification_settings">Varslingsinnstillinger</string> + <string name="pref_notifications">Varslinger</string> + <string name="pref_notifications_summary">Varsle når en ny melding ankommer</string> + <string name="pref_vibrate">Vibrer</string> + <string name="pref_vibrate_summary">Vibrer også når ny melding ankommer</string> + <string name="pref_sound">Lyd</string> + <string name="pref_sound_summary">Spill av ringetone ved varsel</string> + <string name="pref_notification_grace_period">Stilleperiode</string> + <string name="pref_notification_grace_period_summary">Deaktiver varslinger for en kort periode etter at en kopi er mottatt</string> + <string name="pref_advanced_options">Avanserte valg</string> + <string name="pref_never_send_crash">Aldri send feilrettingsrapporter</string> + <string name="pref_never_send_crash_summary">Ved å sende inn stabelsporinger hjelper du den pågående utviklingen av Conversations</string> + <string name="pref_confirm_messages">Bekreft meldinger</string> + <string name="pref_confirm_messages_summary">La din kontakt få vite når du har mottatt og lest en melding</string> + <string name="pref_ui_options">Valg for grensesnitt</string> + <string name="openpgp_error">Feilmelding fra OpenKeychain</string> + <string name="error_decrypting_file">I/O-feil ved dekryptering av fil</string> + <string name="accept">Godta</string> + <string name="error">En feil har inntruffet</string> + <string name="pref_grant_presence_updates">Tillat oppdateringer for tilstedeværelse</string> + <string name="pref_grant_presence_updates_summary">Gi og spør om tilstandsabbonementer på forhånd for kontakter du har opprettet</string> + <string name="subscriptions">Abonnement</string> + <string name="your_account">Din konto</string> + <string name="keys">Nøkler</string> + <string name="send_presence_updates">Send oppdateringer for tilstedeværelse</string> + <string name="receive_presence_updates">Motta oppdateringer for tilstedeværelse</string> + <string name="ask_for_presence_updates">Etterspør oppdateringer for tilstedeværelse</string> + <string name="attach_choose_picture">Velg bilde</string> + <string name="attach_take_picture">Ta bilde</string> + <string name="preemptively_grant">Tillat abbonnementsforespørsel på forhånd</string> + <string name="error_not_an_image_file">Filen du valgte er ikke et bilde</string> + <string name="error_compressing_image">Feil ved konvertering av bildefila</string> + <string name="error_file_not_found">Finner ikke filen</string> + <string name="error_io_exception">Generell I/O-feil. Har du sluppet opp for lagringsplass?</string> + <string name="error_security_exception_during_image_copy">Programmet du brukte til å velge dette bildet ga oss ikke nok tillatelser til å lese filen. \n\n<small>Bruk en annen filbehandler til valg av bilde</small></string> + <string name="account_status_unknown">Ukjent</string> + <string name="account_status_disabled">Midlertidig avskrudd</string> + <string name="account_status_online">Pålogget</string> + <string name="account_status_connecting">Kobler til\u2026</string> + <string name="account_status_offline">Avlogget</string> + <string name="account_status_unauthorized">Ikke tillatt</string> + <string name="account_status_not_found">Fant ikke tjener</string> + <string name="account_status_no_internet">Ingen tilkobling</string> + <string name="account_status_regis_fail">Registrering feilet</string> + <string name="account_status_regis_conflict">Brukernavn allerede i bruk</string> + <string name="account_status_regis_success">Registrering fullført</string> + <string name="account_status_regis_not_sup">Tjeneren støtter ikke registrering</string> + <string name="account_status_security_error">Sikkerhetsfeil</string> + <string name="account_status_incompatible_server">Ukompatibel tjener</string> + <string name="encryption_choice_unencrypted">Ukryptert</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Rediger konto</string> + <string name="mgmt_account_delete">Slett konto</string> + <string name="mgmt_account_disable">Skru av midlertidig</string> + <string name="mgmt_account_publish_avatar">Publiser avatar</string> + <string name="mgmt_account_publish_pgp">Publiser OpenPGP offentlig nøkkel</string> + <string name="mgmt_account_enable">Skru på konto</string> + <string name="mgmt_account_are_you_sure">Bekreft.</string> + <string name="mgmt_account_delete_confirm_text">Hvis du sletter din konto vil hele din konversasjonshistorikk gå tapt</string> + <string name="attach_record_voice">Ta opp stemme</string> + <string name="account_settings_jabber_id">Jabber-ID</string> + <string name="account_settings_password">Passord</string> + <string name="account_settings_example_jabber_id">brukernavn@eksempel.no</string> + <string name="account_settings_confirm_password">Bekreft passord</string> + <string name="password">Passord</string> + <string name="confirm_password">Bekreft passord</string> + <string name="passwords_do_not_match">Passordene samsvarer ikke</string> + <string name="invalid_jid">Dette er ikke en gyldig Jabber-ID</string> + <string name="error_out_of_memory">Slapp opp for minne, bildet er for stort</string> + <string name="add_phone_book_text">Ønsker du å legge %s til i din kontaktliste?</string> + <string name="contact_status_online">pålogget</string> + <string name="contact_status_free_to_chat">tilgjengelig for sludring</string> + <string name="contact_status_away">fraværende</string> + <string name="contact_status_extended_away">borte</string> + <string name="contact_status_do_not_disturb">ikke forstyrr</string> + <string name="contact_status_offline">avlogget</string> + <string name="muc_details_conference">Konferanse</string> + <string name="muc_details_other_members">Andre medlemmer</string> + <string name="server_info_show_more">Tjenerinfo</string> + <string name="server_info_mam">XEP-0313: MAM</string> + <string name="server_info_carbon_messages">XEP-0280: Meldingskarboner</string> + <string name="server_info_csi">XEP-0352: Identifisering av klientstatus</string> + <string name="server_info_blocking">XEP-0191: Blokkeringskommando</string> + <string name="server_info_roster_version">XEP-0237: Kontaktliste-versjonering</string> + <string name="server_info_stream_management">XEP-0198: Behandling av dataflyt</string> + <string name="server_info_pep">XEP-0163: PEP (Avatarer / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP-filopplasting</string> + <string name="server_info_available">tilgjengelig</string> + <string name="server_info_unavailable">utilgjengelig</string> + <string name="missing_public_keys">Manglende annonsering av offentlig nøkkel</string> + <string name="last_seen_now">i syne</string> + <string name="last_seen_min">sist sett for ett minutt siden</string> + <string name="last_seen_mins">sist sett for %d minutter siden</string> + <string name="last_seen_hour">sist sett én time siden</string> + <string name="last_seen_hours">sist sett %d timer siden</string> + <string name="last_seen_day">sist sett i går</string> + <string name="last_seen_days">sist sett for %d dager siden</string> + <string name="never_seen">aldri sett</string> + <string name="install_openkeychain">Kryptert melding. Installer OpenKeychain for å dekryptere.</string> + <string name="unknown_otr_fingerprint">Ukjent OTR-fingeravtrykk</string> + <string name="openpgp_messages_found">OpenPGP-krypterte meldinger funnet</string> + <string name="reception_failed">Mottak mislyktes</string> + <string name="your_fingerprint">Ditt fingeravtrykk</string> + <string name="otr_fingerprint">OTR-fingeravtrykk</string> + <string name="omemo_fingerprint">OMEMO-fingeravtrykk</string> + <string name="omemo_fingerprint_x509">v\\OMEMO-fingeravtrykk</string> + <string name="omemo_fingerprint_selected_message">Meldingens OMEMO-fingeravtrykk</string> + <string name="omemo_fingerprint_x509_selected_message">Meldingens v\\OMEMO-fingeravtrykk</string> + <string name="this_device_omemo_fingerprint">Eget OMEMO-fingeravtrykk</string> + <string name="other_devices">Andre enheter</string> + <string name="trust_omemo_fingerprints">Stol på OMEMO-fingeravtrykk</string> + <string name="fetching_keys">Hener inn nøkler…</string> + <string name="done">Ferdig</string> + <string name="verify">Bekreft</string> + <string name="decrypt">Dekrypter</string> + <string name="conferences">Konferanser</string> + <string name="search">Søk</string> + <string name="create_contact">Opprett kontakt</string> + <string name="enter_contact">Angi kontakt</string> + <string name="join_conference">Ta del i konferanse</string> + <string name="delete_contact">Slett kontakt</string> + <string name="view_contact_details">Vis kontaktdetaljer</string> + <string name="block_contact">Blokker kontakt</string> + <string name="unblock_contact">Avblokker kontakt</string> + <string name="create">Lag</string> + <string name="select">Velg</string> + <string name="contact_already_exists">Kontakten finnes allerede</string> + <string name="join">Ta del i</string> + <string name="conference_address">Konferanse-adresse</string> + <string name="conference_address_example">rom@konferanse.eksempel.no</string> + <string name="save_as_bookmark">Lagre som bokmerke</string> + <string name="delete_bookmark">Slett bokmerke</string> + <string name="bookmark_already_exists">Dette bokmerket finnes allerede</string> + <string name="you">Deg</string> + <string name="action_edit_subject">Rediger temaet for konferansen</string> + <string name="conference_not_found">Fant ikke konferansen</string> + <string name="leave">Forlat</string> + <string name="contact_added_you">Kontakt la deg til i sin liste</string> + <string name="add_back">Gjengjeld tjenesten</string> + <string name="contact_has_read_up_to_this_point">%s har lest hit</string> + <string name="publish">Publiser</string> + <string name="touch_to_choose_picture">Trykk på avataren for å velge blide fra galleriet</string> + <string name="publish_avatar_explanation">Merk, alle som kan se dine tilgjengelighetsoppdateringer vil kunne se dette bildet.</string> + <string name="publishing">Publiserer…</string> + <string name="error_publish_avatar_server_reject">Tjeneren avslo din publisering</string> + <string name="error_publish_avatar_converting">Noe gikk galt under konvertering av bildet ditt</string> + <string name="error_saving_avatar">Kunne ikke lagre avatarbilde til lagringsområde</string> + <string name="or_long_press_for_default">(Eller trykk lenge for å gå tilbake til forvalg)</string> + <string name="error_publish_avatar_no_server_support">Tjeneren din støtter ikke publisering av avatarer</string> + <string name="private_message">hvisket</string> + <string name="private_message_to">til %s</string> + <string name="send_private_message_to">Send privat melding til %s</string> + <string name="connect">Koble til</string> + <string name="account_already_exists">Denne kontoen finnes allerede</string> + <string name="next">Neste</string> + <string name="server_info_session_established">Nåværende økt etablert</string> + <string name="additional_information">Ytterligere informasjon</string> + <string name="skip">Hopp over</string> + <string name="disable_notifications">Deaktiver varslinger</string> + <string name="disable_notifications_for_this_conversation">Skru av varslinger for denne samtalen</string> + <string name="enable">Skru på</string> + <string name="conference_requires_password">Konferansen krever passord</string> + <string name="enter_password">Skriv inn passord</string> + <string name="missing_presence_updates">Mangler tilgjengelighetsoppdateringer fra kontakt</string> + <string name="request_presence_updates">Forespør tilstedeværelseoppdateringer fra din kontakt først.\n\n<small>Dette brukes til å fastslå hvilke(n) klient(er) din kontakt bruker.</small></string> + <string name="request_now">Send forespørsel nå</string> + <string name="delete_fingerprint">Slett fingeravtrykk</string> + <string name="sure_delete_fingerprint">Bekreft fjerning av fingeravtrykk.</string> + <string name="ignore">Ignorer</string> + <string name="without_mutual_presence_updates"><b>Advarsel:</b> Å sende dette uten at tilstandsoppdateringer er i overenstemmelse kan forårsake uventede problemer.\n\n<small>Gå til kontaktdetaljer for å bekrefte dine tilstedeværelsesabonnementer.</small></string> + <string name="pref_encryption_settings">Krypteringsinnstillinger</string> + <string name="pref_force_encryption">Krev ende-til-ende-kryptering</string> + <string name="pref_force_encryption_summary">Alltid send meldinger kryptert (bortsett fra konferanser)</string> + <string name="pref_dont_save_encrypted">Ikke lagre krypterte meldinger</string> + <string name="pref_dont_save_encrypted_summary">Advarsel: Dette kan føre til at meldinger går tapt</string> + <string name="pref_expert_options">Ekspertinnstillinger</string> + <string name="pref_expert_options_summary">Vær forsiktig med disse</string> + <string name="title_activity_about">Om Conversations</string> + <string name="pref_about_conversations_summary">Utgave og lisensinformasjon</string> + <string name="title_pref_quiet_hours">Stille tidsavgrensning</string> + <string name="title_pref_quiet_hours_start_time">Oppstart</string> + <string name="title_pref_quiet_hours_end_time">Avslutning</string> + <string name="title_pref_enable_quiet_hours">Aktiver stille tidsavgrensning</string> + <string name="pref_quiet_hours_summary">Varslinger blir ikke spilt under stilletid</string> + <string name="pref_use_larger_font">Øk tekststørrelse</string> + <string name="pref_use_larger_font_summary">Bruk større tekststørrelser i hele programmet</string> + <string name="pref_use_send_button_to_indicate_status">Forsendelsesknappen indikerer status</string> + <string name="pref_use_indicate_received">Forespørr meldingskvitteringer</string> + <string name="pref_use_indicate_received_summary">Mottatte meldinger vil bli avmerket i grønt hvis støttet</string> + <string name="pref_use_send_button_to_indicate_status_summary">Fargelegg send-knapp for å indikere kontakt-status</string> + <string name="pref_expert_options_other">Annet</string> + <string name="pref_conference_name">Konferanse-rom</string> + <string name="pref_conference_name_summary">Bruk rommets samtaletema istedenfor JID til å identifisere konferanser</string> + <string name="toast_message_otr_fingerprint">OTR-fingeravtrykk kopiert til utklippstavle!</string> + <string name="toast_message_omemo_fingerprint">OMEMO-fingeravtrykk kopiert til utklippstavle!</string> + <string name="conference_banned">Du er bannlyst fra denne konferansen</string> + <string name="conference_members_only">Denne konferansen er forbeholdt medlemmer</string> + <string name="conference_kicked">Du har blitt kastet ut av denne konferansen</string> + <string name="using_account">bruker konto %s</string> + <string name="checking_x">Sjekker %s på HTTP-tjener</string> + <string name="not_connected_try_again">Du er ikke tilkoblet. Prøv igjen senere</string> + <string name="check_x_filesize">Sjekk %s størrelse</string> + <string name="message_options">Meldingsvalg</string> + <string name="copy_text">Kopier tekst</string> + <string name="copy_original_url">Kopier orginal nettadresse</string> + <string name="send_again">Send igjen</string> + <string name="file_url">Filens nettadresse</string> + <string name="message_text">Meldingstekst</string> + <string name="url_copied_to_clipboard">Nettadresse kopiert til utklippstavle</string> + <string name="message_copied_to_clipboard">Melding kopiert til utklippstavle</string> + <string name="image_transmission_failed">Bildeoverføring feilet</string> + <string name="scan_qr_code">Skann QR-kode</string> + <string name="show_qr_code">Vis QR-kode</string> + <string name="show_block_list">Vis blokkeringsliste</string> + <string name="account_details">Kontodetaljer</string> + <string name="verify_otr">Bekreft OTR</string> + <string name="remote_fingerprint">Eksternt fingeravtrykk</string> + <string name="scan">skann</string> + <string name="smp">Sosialistisk-millionær-protokoll</string> + <string name="shared_secret_hint">Ledetråd eller spørsmål</string> + <string name="shared_secret_secret">Delt hemmelighet</string> + <string name="confirm">Bekreft</string> + <string name="in_progress">Underveis</string> + <string name="respond">Svar</string> + <string name="failed">Feilet</string> + <string name="secrets_do_not_match">Hemmelighetene samsvarer ikke</string> + <string name="try_again">Prøv igjen</string> + <string name="finish">Fullfør</string> + <string name="verified">Tiltrodd!</string> + <string name="smp_requested">Kontakt forespurte SMP-verifisering</string> + <string name="no_otr_session_found">Ingen gyldig OTR-økt funnet!</string> + <string name="conversations_foreground_service">Conversations</string> + <string name="pref_keep_foreground_service">Behold tjenesten i forgrunnen</string> + <string name="pref_keep_foreground_service_summary">Forhindrer operativsystemet fra å drepe tilkoblingen din</string> + <string name="pref_export_logs">Eksporter loggføringer</string> + <string name="pref_export_logs_summary">Skriv loggføringer til SD-kort</string> + <string name="notification_export_logs_title">Skriver loggføringer til SD-kort</string> + <string name="choose_file">Velg fil</string> + <string name="receiving_x_file">Mottak av %1$s (%2$d%% fullført)</string> + <string name="download_x_file">Last ned %s</string> + <string name="file">fil</string> + <string name="open_x_file">Åpne %s</string> + <string name="sending_file">forsendelse av (%1$d%% fullført)</string> + <string name="preparing_file">Forbereder fil for oversending</string> + <string name="x_file_offered_for_download">%s tilbudt for nedlasting</string> + <string name="cancel_transmission">Avbryt overføring</string> + <string name="file_transmission_failed">fil-overføring feilet</string> + <string name="file_deleted">Filen har blitt slettet</string> + <string name="no_application_found_to_open_file">Fant inget program til åpning av fil</string> + <string name="could_not_verify_fingerprint">Kunne ikke bekrefte fingeravtrykk</string> + <string name="manually_verify">Bekreft manuelt</string> + <string name="are_you_sure_verify_fingerprint">Bekreft verifisering av din kontakts OTR-fingeravtrykk.</string> + <string name="pref_show_dynamic_tags">Vis dynamiske merkelapper</string> + <string name="pref_show_dynamic_tags_summary">Vis \"bare-les\"-merkelapper under kontakter</string> + <string name="enable_notifications">Aktiver varslinger</string> + <string name="conference_with">Opprett konferanse med…</string> + <string name="no_conference_server_found">Ingen konferanse-tjener funnet</string> + <string name="conference_creation_failed">Opprettelse av konferanse feilet!</string> + <string name="conference_created">Konferanse opprettet!</string> + <string name="secret_accepted">Hemmelighet godtatt!</string> + <string name="reset">Tilbakestill</string> + <string name="account_image_description">Konto-avatar</string> + <string name="copy_otr_clipboard_description">Kopier OTR-fingeravtrykk til utklippstavle</string> + <string name="copy_omemo_clipboard_description">Kopier OMEMO-fingeravtrykk til utklippstavle</string> + <string name="regenerate_omemo_key">Regenerer OMEMO-nøkkel</string> + <string name="wipe_omemo_pep">Rens andre enheter fra PEP</string> + <string name="clear_other_devices">Rens enheter</string> + <string name="clear_other_devices_desc">Bekreft rensinga av alla andre enheter fra OMEMO-kunngjøringen. Neste gang dine enheter kobler til, vil de tilkjennegi seg på ny, men det kan hende de ikke mottar meldinger sendt i mellomtiden.</string> + <string name="purge_key">Tilintetgjør nøkkel</string> + <string name="purge_key_desc_part1">Tilintetgjør denne nøkkelen?</string> + <string name="purge_key_desc_part2">Den vil for all fremtid bli ansett som kompromittert, og du kan aldri starte en økt med den igjen.</string> + <string name="error_no_keys_to_trust_server_error">Ingen brukbare nøkler tilgjengelige for denne kontakten.\nInnhenting av nye nøkler fra tjeneren var ikke vellykket. Kanskje det er noe galt med tjeneren kontakten din bruker?</string> + <string name="error_no_keys_to_trust">Ingen brukbare nøkler tilgjengelige for denne kontakten. Hvis du har tilintetgjort noen av nøklene deres, må de generere nye.</string> + <string name="error_trustkeys_title">Feil</string> + <string name="fetching_history_from_server">Henter inn historikk fra tjener</string> + <string name="no_more_history_on_server">Ikke mer historikk på tjeneren</string> + <string name="updating">Oppdaterer…</string> + <string name="password_changed">Passord endret!</string> + <string name="could_not_change_password">Kunne ikke endre passord</string> + <string name="otr_session_not_started">Send en melding for å igangsette kryptert sludring</string> + <string name="ask_question">Still et spørsmål</string> + <string name="smp_explain_question">Om du og din kontakt har en hemmelighet dere deler som ingen andre vet om (som en intern vits eller hva du spiste til lunsj sist gang dere møttes) kan du bruke det til å bekrefte hverandres fingeravtrykk.\n\nDu gir en ledetråd eller stiller et spørsmål til din kontakt som i sin tur gir et versalsensitivt svar.</string> + <string name="smp_explain_answer">Din kontakt vil bekrefte ditt fingeravtrykk gjennom å utfordre deg med en delt hemmelighet. Din kontakt oppga følgende ledetråd eller spørsmål for hemmeligheten.</string> + <string name="shared_secret_hint_should_not_be_empty">Ledetråden din bør ikke stå tom</string> + <string name="shared_secret_can_not_be_empty">Din delte hemmelighet kan ikke være et tomt felt</string> + <string name="manual_verification_explanation">Jamfør nøye fingeravtrykket vis nedenfor med din kontakts fingeravtrykk.\nDu kan bruke enhver form for betrodd kommunikasjon som kryptert e-post eller en telefonsamtale for å utveksle disse.</string> + <string name="change_password">Endre passord</string> + <string name="current_password">Gjeldende passord</string> + <string name="new_password">Nytt passord</string> + <string name="password_should_not_be_empty">Passord burde ikke være et tomt felt</string> + <string name="enable_all_accounts">Skru på alle kontoer</string> + <string name="disable_all_accounts">Koble fra alle kontoer</string> + <string name="perform_action_with">Utfør handling med</string> + <string name="no_affiliation">Ingen tilknytning</string> + <string name="no_role">Ingen rolle</string> + <string name="outcast">Fredløs</string> + <string name="member">Medlem</string> + <string name="advanced_mode">Avansert modus</string> + <string name="grant_membership">Innlem som medlem</string> + <string name="remove_membership">Tilbakekall medlemskap</string> + <string name="grant_admin_privileges">Innlem som administrator</string> + <string name="remove_admin_privileges">Tilbakekall administratorrettigheter</string> + <string name="remove_from_room">Fjern fra konferanse</string> + <string name="could_not_change_affiliation">Kunne ikke endre tilknytningen til %s</string> + <string name="ban_from_conference">Bannlys fra konferanse</string> + <string name="removing_from_public_conference">Du prøver å fjerne %s fra en offentlig konferanse. Den eneste måten å gjøre det på er å bannlyse denne brukeren for godt.</string> + <string name="ban_now">Bannlys nå</string> + <string name="could_not_change_role">Kunne ikke endre rollen til %s</string> + <string name="public_conference">Konferanse i offentligheten</string> + <string name="private_conference">Privat konferanse kun for medlemmer</string> + <string name="conference_options">Valg for konferanse</string> + <string name="members_only">Privat, kun for medlemmer</string> + <string name="non_anonymous">Ikke-anonym</string> + <string name="moderated">Moderert</string> + <string name="you_are_not_participating">Du deltar ikke</string> + <string name="modified_conference_options">Endret konferanse-valg!</string> + <string name="could_not_modify_conference_options">Kunne ikke endre konferanse-valg</string> + <string name="never">Aldri</string> + <string name="thirty_minutes">30 minutter</string> + <string name="one_hour">1 time</string> + <string name="two_hours">2 timer</string> + <string name="eight_hours">8 timer</string> + <string name="until_further_notice">Til videre beskjed</string> + <string name="pref_input_options">Inndata-valg</string> + <string name="pref_enter_is_send">Enter er forsendelsesknapp</string> + <string name="pref_enter_is_send_summary">Bruk enter for å sende en melding</string> + <string name="pref_display_enter_key">Vis enter-tast</string> + <string name="pref_display_enter_key_summary">Endre smilefjas-tast til en enter-tast</string> + <string name="audio">lyd</string> + <string name="video">film</string> + <string name="image">stillbilde</string> + <string name="pdf_document">PDF-dokument</string> + <string name="apk">Android-app</string> + <string name="vcard">Kontakt</string> + <string name="received_x_file">Mottatt %s</string> + <string name="disable_foreground_service">Skru av forgrunns-tjeneste</string> + <string name="touch_to_open_conversations">Trykk for å åpne Conversations</string> + <string name="avatar_has_been_published">Avatar publisert!</string> + <string name="sending_x_file">Sender %s</string> + <string name="offering_x_file">Tilbyr %s</string> + <string name="hide_offline">Ikke vis frakoblede</string> + <string name="disable_account">Deaktiver konto</string> + <string name="contact_is_typing">%s skriver…</string> + <string name="contact_has_stopped_typing">%s har sluttet å skrive</string> + <string name="pref_chat_states">Varsler for skriving</string> + <string name="pref_chat_states_summary">La din kontakt få vite når du skriver en ny melding</string> + <string name="send_location">Send plasseringsdata</string> + <string name="show_location">Vis plasseringsdata</string> + <string name="no_application_found_to_display_location">Ingen programmer funnet til visning av plasseringsdata</string> + <string name="location">Plasseringsdata</string> + <string name="received_location">Mottok plasseringsdata</string> + <string name="title_undo_swipe_out_conversation">Samtale lukket</string> + <string name="title_undo_swipe_out_muc">Forlot konferansen</string> + <string name="pref_dont_trust_system_cas_title">Ikke stol på systemets CA-er</string> + <string name="pref_dont_trust_system_cas_summary">Alle sertifikat må godkjennes manuelt</string> + <string name="pref_remove_trusted_certificates_title">Fjern sertifikater</string> + <string name="pref_remove_trusted_certificates_summary">Slett sertifikater som er godkjent manuelt</string> + <string name="toast_no_trusted_certs">Ingen manuelt godkjente sertifikater</string> + <string name="dialog_manage_certs_title">Fjern sertifikater</string> + <string name="dialog_manage_certs_positivebutton">Slett innhold i merket område</string> + <string name="dialog_manage_certs_negativebutton">Avbryt</string> + <plurals name="toast_delete_certificates"> + <item quantity="one">%d sertifikat slettet</item> + <item quantity="other">%d sertifikater slettet</item> + </plurals> + <plurals name="select_contact"> + <item quantity="one">Velg %d kontakt</item> + <item quantity="other">Velg %d kontakter</item> + </plurals> + <string name="pref_quick_action_summary">Erstatt forsendelsesknapp med hurtighandling</string> + <string name="pref_quick_action">Hurtighandling</string> + <string name="none">Ingen</string> + <string name="recently_used">Senest brukt</string> + <string name="choose_quick_action">Velg hurtighendelse</string> + <string name="search_for_contacts_or_groups">Søk etter kontakter eller grupper</string> + <string name="send_private_message">Send privat melding</string> + <string name="user_has_left_conference">%s har forlatt konferansen!</string> + <string name="username">Brukernavn</string> + <string name="username_hint">Brukernavn</string> + <string name="invalid_username">Dette er ikke et gyldig brukernavn</string> + <string name="download_failed_server_not_found">Nedlasting feilet: Fant ikke tjener</string> + <string name="download_failed_file_not_found">Nedlasting feilet: Fant ikke fila</string> + <string name="download_failed_could_not_connect">Nedlasting feilet: Kunne ikke koble til tjeneren</string> + <string name="account_status_tor_unavailable">Tor-nettverk utilgjengelig</string> + <string name="server_info_broken">Knekt</string> + <string name="pref_presence_settings">Tilstedeværelse-innstillinger</string> + <string name="pref_away_when_screen_off">Borte når skjermen er av</string> + <string name="pref_away_when_screen_off_summary">Markerer din ressurs som borte når skjermen er avskrudd</string> + <string name="pref_xa_on_silent_mode">Ikke tilgjengelig i stille-modus</string> + <string name="pref_xa_on_silent_mode_summary">Markerer din ressurs som \'ikke tilgjengelig\' når enheten er i stille-modus.</string> + <string name="action_add_account_with_certificate">Legg til konto med sertifikat</string> + <string name="unable_to_parse_certificate">Kunne ikke behandle sertifikat</string> + <string name="authenticate_with_certificate">La stå tom for bekreftelse med sertifikat</string> + <string name="captcha_ocr">CAPTCHA-tekst</string> + <string name="captcha_required">CAPTCHA-påkrevd</string> + <string name="captcha_hint">skriv inn teksten på bildet</string> + <string name="certificate_chain_is_not_trusted">Sertifikat-kjeden er ikke betrodd</string> + <string name="jid_does_not_match_certificate">Jabber-ID-en samsvarer ikke med sertifikatet</string> + <string name="action_renew_certificate">Forny sertifikat</string> + <string name="error_fetching_omemo_key">Feil ved innhenting av OMEMO-nøkkel!</string> + <string name="verified_omemo_key_with_certificate">Bekreftet OMEMO-nøkkel med sertifikat!</string> + <string name="device_does_not_support_certificates">Din enhet støtter ikke valg av klientsertifikat!</string> + <string name="pref_connection_options">Tilkoblingsalternativ</string> + <string name="account_settings_hostname">Tjenernavn</string> + <string name="account_settings_port">Port</string> + <string name="not_a_valid_port">Dette er ikke et gyldig portnummer</string> + <string name="not_valid_hostname">Dette er ikke et gyldig tjenernavn</string> + <string name="connected_accounts">%1$d av %2$d kontoer tilkoblet</string> + <plurals name="x_messages"> + <item quantity="one">%d melding</item> + <item quantity="other">%dmeldinger</item> + </plurals> + <string name="shared_file_with_x">Fil delt med %s</string> + <string name="shared_image_with_x">Bilde delt med %s</string> + <string name="no_storage_permission">Conversations trenger tilgang til eksternt lagringsmedie</string> + <string name="sync_with_contacts">Synkroniser med kontakter</string> + <string name="sync_with_contacts_long">Conversations vil jamføre din XMPP-kontaktliste med dine kontakter for å vise dem med navn og profilbilde.\n\nConversations leser bare dine kontakter for å jamføre dem lokalt, uten å laste dem opp til tjeneren din.\n\nDu kommer nå til å bli spurt om tilgangstillatelse til dine kontakter.</string> + <string name="certificate_information">Sertifikatsinformasjon</string> + <string name="certificate_subject">Emne</string> + <string name="certificate_issuer">Utsteder</string> + <string name="certificate_cn">Vanlig navn</string> + <string name="certificate_o">Organisasjon</string> + <string name="certificate_sha1">SHA1</string> + <string name="certicate_info_not_available">(Ikke tilgjengelig)</string> + <string name="certificate_not_found">Fant ikke noe sertifikat</string> + <string name="notify_on_all_messages">Varsle ved alle meldinger</string> + <string name="notify_only_when_highlighted">Varsle bare når fremhevet</string> + <string name="notify_never">Varslinger deaktivert</string> + <string name="notify_paused">Varslinger pauset</string> + <string name="always">Alltid</string> + <string name="automatically">Automatisk</string> + <string name="battery_optimizations_enabled">Batterioptimaliseringer aktivert</string> + <string name="battery_optimizations_enabled_explained">Enheten din gjør noen tunge batterioptimaliseringer på Conversations som kan føre til forsinkede varslinger eller tap av meldinger.\nDet anbefales at du deaktiverer disse.</string> + <string name="battery_optimizations_enabled_dialog">Enheten din gjør noen tunge batterioptimaliseringer på Conversations som kan føre til forsinkede varslinger eller tap av meldinger.\n\nDu vil nå bli bedt om å deaktivere disse.</string> + <string name="disable">Deaktiver</string> + <string name="selection_too_large">Det valgte området er for stort</string> +</resources> diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index 2873a793..33018c9d 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -2,25 +2,25 @@ <resources> <string name="action_settings">Instellingen</string> <string name="action_add">Nieuw gesprek</string> - <string name="action_accounts">Beheer account</string> - <string name="action_end_conversation">Beëindig gesprek</string> + <string name="action_accounts">Accounts beheren</string> + <string name="action_end_conversation">Gesprek beëindigen</string> <string name="action_contact_details">Contactgegevens</string> <string name="action_muc_details">Gespreksgegevens</string> <string name="action_secure">Beveiligd gesprek</string> - <string name="action_add_account">Voeg account toe</string> - <string name="action_edit_contact">Verander naam</string> - <string name="action_add_phone_book">Voeg toe aan telefoonboek</string> - <string name="action_delete_contact">Verwijder uit lijst</string> - <string name="action_block_contact">Blokkeer contact</string> - <string name="action_unblock_contact">Deblokkeer contact</string> - <string name="action_block_domain">Blokkeer domein</string> - <string name="action_unblock_domain">Deblokkeer domein</string> - <string name="title_activity_manage_accounts">Beheer accounts</string> + <string name="action_add_account">Account toevoegen</string> + <string name="action_edit_contact">Naam veranderen</string> + <string name="action_add_phone_book">Toevoegen aan adresboek</string> + <string name="action_delete_contact">Verwijderen uit lijst</string> + <string name="action_block_contact">Contact blokkeren</string> + <string name="action_unblock_contact">Contact deblokkeren</string> + <string name="action_block_domain">Domein blokkeren</string> + <string name="action_unblock_domain">Domein deblokkeren</string> + <string name="title_activity_manage_accounts">Accounts beheren</string> <string name="title_activity_settings">Instellingen</string> <string name="title_activity_conference_details">Groepsgespreksgegevens</string> <string name="title_activity_contact_details">Contactgegevens</string> <string name="title_activity_sharewith">Delen met gesprek</string> - <string name="title_activity_start_conversation">Start gesprek</string> + <string name="title_activity_start_conversation">Gesprek starten</string> <string name="title_activity_choose_contact">Kies contact</string> <string name="title_activity_block_list">Geblokkeerde contacten</string> <string name="just_now">net</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d min. geleden</string> <string name="unread_conversations">ongelezen gesprekken</string> <string name="sending">versturen…</string> - <string name="encrypted_message">Bericht aan het ontsleutelen. Even geduld…</string> + <string name="message_decrypting">Bericht aan het ontsleutelen. Even geduld…</string> + <string name="pgp_message">OpenPGP-versleuteld bericht</string> <string name="nick_in_use">Naam is al in gebruik</string> <string name="admin">Beheerder</string> <string name="owner">Eigenaar</string> @@ -42,101 +43,102 @@ <string name="unblock_domain_text">Alle contacten van %s deblokkeren?</string> <string name="contact_blocked">Contact geblokkeerd</string> <string name="remove_bookmark_text">Wil je %s als bladwijzer verwijderen? Het gesprek met deze account zal niet worden verwijderd.</string> - <string name="register_account">Registreer nieuwe account op server</string> - <string name="change_password_on_server">Verander wachtwoord op server</string> - <string name="share_with">Deel met</string> - <string name="start_conversation">Start gesprek</string> - <string name="invite_contact">Nodig contact uit</string> + <string name="register_account">Nieuwe account op server registreren</string> + <string name="change_password_on_server">Wachtwoord op server veranderen</string> + <string name="share_with">Delen met…</string> + <string name="start_conversation">Gesprek starten</string> + <string name="invite_contact">Contact uitnodigen</string> <string name="contacts">Contacten</string> - <string name="cancel">Annuleer</string> - <string name="set">Stel in</string> - <string name="add">Voeg toe</string> - <string name="edit">Bewerk</string> - <string name="delete">Verwijder</string> - <string name="block">Blokkeer</string> - <string name="unblock">Deblokkeer</string> - <string name="save">Sla op</string> - <string name="ok">OK</string> + <string name="cancel">Annuleren</string> + <string name="set">Instellen</string> + <string name="add">Toevoegen</string> + <string name="edit">Bewerken</string> + <string name="delete">Verwijderen</string> + <string name="block">Blokkeren</string> + <string name="unblock">Deblokkeren</string> + <string name="save">Opslaan</string> + <string name="ok">Oké</string> <string name="crash_report_title">Conversations is gecrasht</string> - <string name="crash_report_message">Door het versturen van crash rapportages help je de ontwikkeling van Conversations.\n\n<b>Waarschuwing:</b> Deze app zal je XMPP account gebruiken om de crash rapportages te versturen naar de ontwikkelaars.</string> + <string name="crash_report_message">Door het versturen van crashrapportages help je de ontwikkeling van Conversations.\n\n<b>Waarschuwing:</b> Deze app zal je XMPP-account gebruiken om de crashrapportages te versturen naar de ontwikkelaars.</string> <string name="send_now">Nu versturen</string> <string name="send_never">Niet opnieuw vragen</string> <string name="problem_connecting_to_account">Account verbinden mislukt</string> <string name="problem_connecting_to_accounts">Verbinden met meerdere accounts mislukt</string> <string name="touch_to_fix">Raak hier aan om accounts te beheren</string> - <string name="attach_file">Voeg bestand bij</string> - <string name="not_in_roster">Het contact is geen onderdeel van uw lijst. Wil je het toevoegen?</string> - <string name="add_contact">Voeg contact toe</string> + <string name="attach_file">Bestand bijvoegen</string> + <string name="not_in_roster">Het contact is geen onderdeel van je lijst. Wil je hem/haar toevoegen?</string> + <string name="add_contact">Contact toevoegen</string> <string name="send_failed">afleveren mislukt</string> <string name="send_rejected">geweigerd</string> <string name="preparing_image">Bezig met voorbereiden van versturen van afbeelding</string> - <string name="action_clear_history">Wis geschiedenis</string> - <string name="clear_conversation_history">Wis gespreksgeschiedenis</string> + <string name="action_clear_history">Geschiedenis wissen</string> + <string name="clear_conversation_history">Gespreksgeschiedenis wissen</string> <string name="clear_histor_msg">Wil je alle berichten in dit gesprek verwijderen?\n\n<b>Waarschuwing:</b> Dit zal geen invloed hebben op de berichten opgeslagen op andere apparaten of servers.</string> - <string name="delete_messages">Verwijder berichten</string> - <string name="also_end_conversation">Beëindig dit gesprek na afloop</string> + <string name="delete_messages">Berichten verwijderen</string> + <string name="also_end_conversation">Dit gesprek na afloop beëindigen</string> <string name="choose_presence">Kies aanwezigheid om te tonen aan contact</string> - <string name="send_plain_text_message">Verstuur eenvoudig tekst bericht</string> - <string name="send_otr_message">Verstuur OTR versleuteld bericht</string> - <string name="send_pgp_message">Verstuur OpenPGP versleuteld bericht</string> + <string name="send_unencrypted_message">Verstuur onversleuteld bericht</string> + <string name="send_otr_message">Verstuur OTR-versleuteld bericht</string> + <string name="send_omemo_message">Verstuur OMEMO-versleuteld bericht</string> + <string name="send_omemo_x509_message">Verstuur v\\OMEMO-versleuteld bericht</string> + <string name="send_pgp_message">Verstuur OpenPGP-versleuteld bericht</string> <string name="your_nick_has_been_changed">Je naam is veranderd</string> <string name="send_unencrypted">Verstuur onversleuteld</string> <string name="decryption_failed">Ontsleutelen mislukt. Misschien heb je niet de juiste private sleutel.</string> <string name="openkeychain_required">OpenKeychain</string> <string name="openkeychain_required_long">Conversations gebruikt een derde partij app genaamd <b>OpenKeychain</b> om berichten te versleutelen en ontsleutelen, en om publieke sleutels te beheren.\n\nOpenKeychain is beschikbaar onder de GPLv3 en beschikbaar op F-Droid en Google Play.\n\n<small>(Herstart Conversations na installatie.)</small></string> - <string name="restart">Herstart</string> - <string name="install">Installeer</string> - <string name="offering">offering…</string> + <string name="restart">Herstarten</string> + <string name="install">Installeren</string> + <string name="openkeychain_not_installed">Gelieve OpenKeychain te installeren</string> + <string name="offering">bezig met aanbieden…</string> <string name="waiting">wachten…</string> - <string name="no_pgp_key">Geen OpenPGP sleutel gevonden</string> + <string name="no_pgp_key">Geen OpenPGP-sleutel gevonden</string> <string name="contact_has_no_pgp_key">Conversations kan je berichten niet versleutelen omdat je contact geen publieke sleutel heeft ingesteld.\n\n<small>Vraag je contact om OpenPGP te configureren.</small></string> - <string name="no_pgp_keys">Geen OpenPGP sleutels gevonden</string> + <string name="no_pgp_keys">Geen OpenPGP-sleutels gevonden</string> <string name="contacts_have_no_pgp_keys">Conversations kan je berichten niet versleutelen omdat je contacten geen publieke sleutel hebben ingesteld.\n\n<small>Vraag je contacten om OpenPGP te configureren.</small></string> - <string name="encrypted_message_received"><i>Versleuteld bericht ontvangen. Raak aan om te bekijken en te ontsleutelen.</i></string> + <string name="encrypted_message_received"><i>Versleuteld bericht ontvangen. Raak aan om te ontsleutelen.</i></string> <string name="pref_general">Algemeen</string> - <string name="pref_xmpp_resource">XMPP resource</string> - <string name="pref_xmpp_resource_summary">De naam waarmee deze client zich identificeert</string> - <string name="pref_accept_files">Accepteer bestanden</string> - <string name="pref_accept_files_size_summary">Accepteer automatisch bestanden kleiner dan…</string> - <string name="pref_notification_settings">Meldingsinstellingen</string> + <string name="pref_xmpp_resource">XMPP-bron</string> + <string name="pref_xmpp_resource_summary">De naam waarmee deze cliënt zich identificeert</string> + <string name="pref_accept_files">Aanvaard bestanden</string> + <string name="pref_accept_files_summary">Aanvaard automatisch bestanden kleiner dan…</string> + <string name="pref_notification_settings">Melding</string> <string name="pref_notifications">Meldingen</string> <string name="pref_notifications_summary">Melding als een nieuw bericht arriveert</string> <string name="pref_vibrate">Trillen</string> <string name="pref_vibrate_summary">Tril ook wanneer een nieuw bericht arriveert</string> <string name="pref_sound">Geluid</string> - <string name="pref_sound_summary">Speel ringtone af bij melding</string> - <string name="pref_conference_notifications">Groepsgespreksmeldingen</string> - <string name="pref_conference_notifications_summary">Toon altijd meldingen als er nieuwe berichten arriveren in groepsgesprekken in plaats van alleen wanneer gemarkeerd</string> + <string name="pref_sound_summary">Speel beltoon af bij melding</string> <string name="pref_notification_grace_period">Uitstelperiode voor meldingen</string> - <string name="pref_notification_grace_period_summary">Zet meldingen voor korte tijd uit als er een carbon copy wordt ontvangen</string> - <string name="pref_advanced_options">Geavanceerde instellingen</string> - <string name="pref_never_send_crash">Verstuur nooit crash rapportages</string> - <string name="pref_never_send_crash_summary">Door crash rapportages te versturen help je de ontwikkeling van Conversations</string> + <string name="pref_notification_grace_period_summary">Schakel meldingen voor korte tijd uit als er een carbon copy wordt ontvangen</string> + <string name="pref_advanced_options">Geavanceerd</string> + <string name="pref_never_send_crash">Verstuur nooit crashrapportages</string> + <string name="pref_never_send_crash_summary">Door crashrapportages te versturen help je de ontwikkeling van Conversations</string> <string name="pref_confirm_messages">Bevestig berichten</string> <string name="pref_confirm_messages_summary">Laat je contacten weten wanneer je berichten hebt ontvangen en gelezen</string> - <string name="pref_ui_options">UI opties</string> + <string name="pref_ui_options">Gebruikersomgeving</string> <string name="openpgp_error">OpenKeychain rapporteerde een fout</string> - <string name="error_decrypting_file">I/O fout tijdens ontsleutelen bestand</string> - <string name="accept">Aanvaard</string> + <string name="error_decrypting_file">I/O-fout tijdens ontsleutelen van bestand</string> + <string name="accept">Aanvaarden</string> <string name="error">Er is een fout opgetreden</string> <string name="pref_grant_presence_updates">Verleen toestemming voor aanwezigheidsupdates</string> - <string name="pref_grant_presence_updates_summary">Op voorhand toestemming verlenen en vragen aan contacten die je hebt aangemaakt</string> + <string name="pref_grant_presence_updates_summary">Op voorhand toestemming voor aanwezigheidsabonnementen verlenen en vragen aan contacten die je hebt aangemaakt</string> <string name="subscriptions">Abonnementen</string> <string name="your_account">Je account</string> <string name="keys">Sleutels</string> <string name="send_presence_updates">Verstuur aanwezigheidsupdates</string> <string name="receive_presence_updates">Ontvang aanwezigheidsupdates</string> <string name="ask_for_presence_updates">Vraag naar aanwezigheidsupdates</string> - <string name="attach_choose_picture">Kies afbeelding</string> - <string name="attach_take_picture">Neem foto</string> + <string name="attach_choose_picture">Afbeelding kiezen</string> + <string name="attach_take_picture">Foto nemen</string> <string name="preemptively_grant">Op voorhand toestemming verlenen voor abonneren</string> <string name="error_not_an_image_file">Het bestand dat je gekozen hebt is geen afbeelding</string> <string name="error_compressing_image">Fout tijdens converteren van afbeelding</string> <string name="error_file_not_found">Bestand niet gevonden</string> - <string name="error_io_exception">Algemene I/O fout. Misschien is er geen opslagruimte meer beschikbaar?</string> - <string name="error_security_exception_during_image_copy">De app die je gebruikte om de afbeelding te selecteren heeft niet voldoende toegang geleverd om het bestand te lezen.\n\n<small>Gebruik een andere app om een afbeelding te kiezen</small></string> + <string name="error_io_exception">Algemene I/O-fout. Misschien is er geen opslagruimte meer beschikbaar?</string> + <string name="error_security_exception_during_image_copy">De app die je gebruikte om de afbeelding te selecteren heeft niet voldoende toegang geleverd om het bestand te lezen.\n\n<small>Gebruik een andere bestandsbeheerder om een afbeelding te kiezen</small></string> <string name="account_status_unknown">Onbekend</string> - <string name="account_status_disabled">Tijdelijk uitgezet</string> + <string name="account_status_disabled">Tijdelijk uitgeschakeld</string> <string name="account_status_online">Online</string> <string name="account_status_connecting">Verbinden\u2026</string> <string name="account_status_offline">Offline</string> @@ -144,33 +146,34 @@ <string name="account_status_not_found">Server niet gevonden</string> <string name="account_status_no_internet">Geen verbinding</string> <string name="account_status_regis_fail">Registratie mislukt</string> - <string name="account_status_regis_conflict">Gebruikersnaam bezet</string> - <string name="account_status_regis_success">Registratie compleet</string> + <string name="account_status_regis_conflict">Gebruikersnaam is al in gebruik</string> + <string name="account_status_regis_success">Registratie voltooid</string> <string name="account_status_regis_not_sup">Server ondersteunt geen registratie</string> <string name="account_status_security_error">Fout bij beveiliging</string> <string name="account_status_incompatible_server">Incompatibele server</string> - <string name="encryption_choice_none">Onversleuteld</string> + <string name="encryption_choice_unencrypted">Onversleuteld</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> - <string name="mgmt_account_edit">Bewerk account</string> - <string name="mgmt_account_delete">Verwijder</string> - <string name="mgmt_account_disable">Tijdelijk uitzetten</string> - <string name="mgmt_account_publish_avatar">Publish avatar</string> - <string name="mgmt_account_publish_pgp">Publish OpenPGP public key</string> - <string name="mgmt_account_enable">Aanzetten</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Account bewerken</string> + <string name="mgmt_account_delete">Account verwijderen</string> + <string name="mgmt_account_disable">Tijdelijk uitschakelen</string> + <string name="mgmt_account_publish_avatar">Avatar publiceren</string> + <string name="mgmt_account_publish_pgp">Publiceer OpenPGP publieke sleutel</string> + <string name="mgmt_account_enable">Account inschakelen</string> <string name="mgmt_account_are_you_sure">Ben je zeker?</string> <string name="mgmt_account_delete_confirm_text">Als je je account verwijdert wordt je volledige gespreksgeschiedenis gewist</string> - <string name="attach_record_voice">Neem stem op</string> - <string name="account_settings_jabber_id">Jabber ID:</string> + <string name="attach_record_voice">Stem opnemen</string> + <string name="account_settings_jabber_id">Jabber-ID:</string> <string name="account_settings_password">Wachtwoord:</string> <string name="account_settings_example_jabber_id">gebruikersnaam@voorbeeld.nl</string> - <string name="account_settings_confirm_password">Bevestig wachtwoord:</string> + <string name="account_settings_confirm_password">Wachtwoord bevestigen</string> <string name="password">Wachtwoord</string> - <string name="confirm_password">Bevestig wachtwoord</string> + <string name="confirm_password">Wachtwoord bevestigen</string> <string name="passwords_do_not_match">Wachtwoorden komen niet overeen</string> - <string name="invalid_jid">Dit is geen geldig Jabber ID</string> + <string name="invalid_jid">Dit is geen geldige Jabber-ID</string> <string name="error_out_of_memory">Geen geheugen beschikbaar. Afbeelding is te groot</string> - <string name="add_phone_book_text">Wil je %s toevoegen aan de contactenlijst op je telefoon?</string> + <string name="add_phone_book_text">Wil je %s toevoegen aan je adresboek?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">beschikbaar</string> <string name="contact_status_away">weg</string> @@ -179,17 +182,19 @@ <string name="contact_status_offline">offline</string> <string name="muc_details_conference">Groepsgesprek</string> <string name="muc_details_other_members">Andere leden</string> - <string name="server_info_show_more">Server info</string> + <string name="server_info_show_more">Server-info</string> <string name="server_info_mam">XEP-0313: MAM</string> <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> <string name="server_info_csi">XEP-0352: Client State Indication</string> <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> + <string name="server_info_push">XEP-0357: Push</string> <string name="server_info_available">beschikbaar</string> <string name="server_info_unavailable">niet beschikbaar</string> - <string name="missing_public_keys">Ontbrekende publieke sleutel aankondigingen</string> + <string name="missing_public_keys">Ontbrekende publieke sleutel-aankondigingen</string> <string name="last_seen_now">zonet voor het laatst gezien</string> <string name="last_seen_min">1 minuut geleden voor het laatst gezien</string> <string name="last_seen_mins">%d minuten geleden voor het laatst gezien</string> @@ -199,22 +204,33 @@ <string name="last_seen_days">%d dagen geleden voor het laatst gezien</string> <string name="never_seen">nog nooit gezien</string> <string name="install_openkeychain">Versleuteld bericht. Installeer OpenKeychain om te ontsleutelen.</string> - <string name="unknown_otr_fingerprint">Onbekende OTR vingerafdruk</string> + <string name="unknown_otr_fingerprint">Onbekende OTR-vingerafdruk</string> <string name="openpgp_messages_found">OpenPGP-versleutelde berichten gevonden</string> <string name="reception_failed">Ontvangen mislukt</string> <string name="your_fingerprint">Jouw vingerafdruk</string> - <string name="otr_fingerprint">OTR vingerafdruk</string> - <string name="verify">Bevestig</string> - <string name="decrypt">Ontsleutel</string> + <string name="otr_fingerprint">OTR-vingerafdruk</string> + <string name="omemo_fingerprint">OMEMO-vingerafdruk</string> + <string name="omemo_fingerprint_x509">v\\OMEMO-vingerafdruk</string> + <string name="omemo_fingerprint_selected_message">OMEMO-vingerafdruk van bericht</string> + <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO-vingerafdruk van bericht</string> + <string name="this_device_omemo_fingerprint">Eigen OMEMO-vingerafdruk</string> + <string name="other_devices">Andere apparaten</string> + <string name="trust_omemo_fingerprints">Vertrouw OMEMO-vingerafdrukken</string> + <string name="fetching_keys">Sleutels ophalen…</string> + <string name="done">Klaar</string> + <string name="verify">Bevestigen</string> + <string name="decrypt">Ontsleutelen</string> <string name="conferences">Groepsgesprekken</string> <string name="search">Zoeken</string> - <string name="create_contact">Maak contact aan</string> + <string name="create_contact">Contact aanmaken</string> + <string name="enter_contact">Contact invoeren</string> <string name="join_conference">Aan groepsgesprek deelnemen</string> - <string name="delete_contact">Verwijder contact</string> - <string name="view_contact_details">Bekijk contactgegevens</string> - <string name="block_contact">Blokkeer contact</string> - <string name="unblock_contact">Deblokkeer contact</string> + <string name="delete_contact">Contact verwijderen</string> + <string name="view_contact_details">Contactgegevens bekijken</string> + <string name="block_contact">Contact blokkeren</string> + <string name="unblock_contact">Contact deblokkeren</string> <string name="create">Aanmaken</string> + <string name="select">Selecteren</string> <string name="contact_already_exists">Het contact bestaat al</string> <string name="join">Deelnemen</string> <string name="conference_address">Gespreksadres</string> @@ -223,15 +239,16 @@ <string name="delete_bookmark">Bladwijzer verwijderen</string> <string name="bookmark_already_exists">Deze bladwijzer bestaat al</string> <string name="you">Jij</string> - <string name="action_edit_subject">Onderwerp groepsgesprek bewerken</string> + <string name="action_edit_subject">Onderwerp van groepsgesprek bewerken</string> <string name="conference_not_found">Groepsgesprek niet gevonden</string> + <string name="conference_unknown_error">Onbekende fout ontvangen</string> <string name="leave">Verlaten</string> <string name="contact_added_you">Contact heeft je toegevoegd aan zijn/haar contacten</string> <string name="add_back">Contact toevoegen aan eigen contacten</string> <string name="contact_has_read_up_to_this_point">%s heeft tot hier gelezen</string> - <string name="publish">Publiceer</string> + <string name="publish">Publiceren</string> <string name="touch_to_choose_picture">Raak avatar aan om een foto uit de galerij te kiezen</string> - <string name="publish_avatar_explanation"><b>Aandacht:</b> Iedereen die je aanwezigheidsupdates ontvangt zal deze foto kunnen zien.</string> + <string name="publish_avatar_explanation">Let op: iedereen die je aanwezigheidsupdates ontvangt zal deze foto kunnen zien.</string> <string name="publishing">Publiceren…</string> <string name="error_publish_avatar_server_reject">De server weigerde de publicatie van je afbeelding</string> <string name="error_publish_avatar_converting">Fout bij converteren van afbeelding</string> @@ -240,19 +257,18 @@ <string name="error_publish_avatar_no_server_support">Je server ondersteunt de publicatie van avatars niet</string> <string name="private_message">gefluisterd</string> <string name="private_message_to">naar %s</string> - <string name="send_private_message_to">Stuur privébericht naar %s</string> + <string name="send_private_message_to">Privébericht sturen naar %s</string> <string name="connect">Verbinden</string> <string name="account_already_exists">Deze account bestaat al</string> <string name="next">Volgende</string> <string name="server_info_session_established">Huidige sessie gevestigd</string> <string name="additional_information">Bijkomstige informatie</string> <string name="skip">Overslaan</string> - <string name="disable_notifications">Meldingen uitzetten</string> - <string name="disable_notifications_for_this_conversation">Meldingen uitzetten voor dit gesprek</string> - <string name="notifications_disabled">Meldingen zijn uitgezet</string> - <string name="enable">Aanzetten</string> + <string name="disable_notifications">Meldingen uitschakelen</string> + <string name="disable_notifications_for_this_conversation">Meldingen uitschakelen voor dit gesprek</string> + <string name="enable">Inschakelen</string> <string name="conference_requires_password">Wachtwoord nodig voor toegang tot groepsgesprek</string> - <string name="enter_password">Wachtwoord:</string> + <string name="enter_password">Wachtwoord invoeren</string> <string name="missing_presence_updates">Ontbrekende aanwezigheidsupdates van contact</string> <string name="request_presence_updates">Vraag eerst aanwezigheidsupdates van je contact aan.\n\n<small>Dit wordt gebruikt om te bepalen welke client(s) je contact gebruikt.</small></string> <string name="request_now">Nu aanvragen</string> @@ -260,19 +276,21 @@ <string name="sure_delete_fingerprint">Ben je zeker dat je deze vingerafdruk wil verwijderen?</string> <string name="ignore">Negeren</string> <string name="without_mutual_presence_updates"><b>Waarschuwing:</b> Dit verzenden zonder wederzijdse aanwezigheidsupdates kan voor onverwachte problemen zorgen.\n\n<small>Ga naar contactgegevens om je aanwezigheidsupdates te bevestigen.</small></string> - <string name="pref_encryption_settings">Versleutelingsinstellingen</string> - <string name="pref_force_encryption">Verplicht end-to-end versleuteling</string> + <string name="pref_security_settings">Beveiliging</string> + <string name="pref_force_encryption">Verplicht end-to-end-versleuteling</string> <string name="pref_force_encryption_summary">Stuur berichten altijd versleuteld (behalve in groepsgesprekken)</string> + <string name="pref_allow_message_correction">Berichtverbetering toestaan</string> + <string name="pref_allow_message_correction_summary">Sta je contacten toe hun berichten na het versturen te verbeteren</string> <string name="pref_dont_save_encrypted">Sla versleutelde berichten niet op</string> - <string name="pref_dont_save_encrypted_summary"><b>Waarschuwing:</b> Dit kan leiden tot verlies van berichten</string> - <string name="pref_expert_options">Expert-instellingen</string> + <string name="pref_dont_save_encrypted_summary">Waarschuwing: dit kan leiden tot verlies van berichten</string> + <string name="pref_expert_options">Instellingen voor experts</string> <string name="pref_expert_options_summary">Wees voorzichtig met deze instellingen</string> <string name="title_activity_about">Over Conversations</string> <string name="pref_about_conversations_summary">Build en licentie-informatie</string> <string name="title_pref_quiet_hours">Stille uren</string> <string name="title_pref_quiet_hours_start_time">Begintijd</string> <string name="title_pref_quiet_hours_end_time">Eindtijd</string> - <string name="title_pref_enable_quiet_hours">Stille uren aanzetten</string> + <string name="title_pref_enable_quiet_hours">Stille uren inschakelen</string> <string name="pref_quiet_hours_summary">Tijdens stille uren worden meldingen onderdrukt</string> <string name="pref_use_larger_font">Vergroot lettergrootte</string> <string name="pref_use_larger_font_summary">Gebruik grotere lettertypes over de hele app</string> @@ -283,30 +301,33 @@ <string name="pref_expert_options_other">Andere</string> <string name="pref_conference_name">Groepsgespreksnaam</string> <string name="pref_conference_name_summary">Gebruik onderwerp van kamer ipv JID om groepsgesprekken te identificeren</string> - <string name="toast_message_otr_fingerprint">OTR vingerafdruk naar klembord gekopieerd!</string> + <string name="pref_autojoin">Automatisch deelnemen aan groepsgesprekken</string> + <string name="pref_autojoin_summary">Respecteer de autojoin-vlag bij groepsgespreksbladwijzers</string> + <string name="toast_message_otr_fingerprint">OTR-vingerafdruk gekopieerd naar klembord!</string> + <string name="toast_message_omemo_fingerprint">OMEMO-vingerafdruk gekopieerd naar klembord!</string> <string name="conference_banned">Je bent verbannen uit dit groepsgesprek</string> <string name="conference_members_only">Dit groepsgesprek is enkel voor leden</string> <string name="conference_kicked">Je bent uit dit groepsgesprek geschopt</string> - <string name="using_account">account %s gebruiken</string> + <string name="using_account">met account %s</string> + <string name="checking_x">%s op HTTP-host nakijken</string> <string name="not_connected_try_again">Je bent niet verbonden. Probeer later opnieuw</string> - <string name="check_x_filesize">Bekijk bestandsgrootte van %s</string> + <string name="check_x_filesize">Bestandsgrootte van %s controleren</string> <string name="message_options">Berichtopties</string> - <string name="copy_text">Kopieer tekst</string> - <string name="copy_original_url">Kopieer oorspronkelijke URL</string> - <string name="send_again">Verstuur opnieuw</string> + <string name="copy_text">Tekst kopiëren</string> + <string name="copy_original_url">Oorspronkelijke URL kopiëren</string> + <string name="send_again">Opnieuw versturen</string> <string name="file_url">Bestands-URL</string> <string name="message_text">Berichttekst</string> <string name="url_copied_to_clipboard">URL gekopieerd naar klembord</string> <string name="message_copied_to_clipboard">Bericht gekopieerd naar klembord</string> <string name="image_transmission_failed">Versturen van afbeelding mislukt</string> - <string name="scan_qr_code">Scan QR code</string> - <string name="show_qr_code">Toon QR code</string> - <string name="show_block_list">Toon geblokkeerde contacten</string> + <string name="scan_qr_code">QR-code scannen</string> + <string name="show_qr_code">QR-code tonen</string> + <string name="show_block_list">Geblokkeerde contacten weergeven</string> <string name="account_details">Accountgegevens</string> - <string name="verify_otr">Bevestig OTR</string> + <string name="verify_otr">OTR bevestigen</string> <string name="remote_fingerprint">Externe vingerafdruk</string> <string name="scan">scan</string> - <string name="or_touch_phones">(of raak gsm\'s aan)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Hint of vraag</string> <string name="shared_secret_secret">Gedeeld geheim</string> @@ -321,17 +342,20 @@ <string name="smp_requested">Contact vraagt SMP-bevestiging</string> <string name="no_otr_session_found">Geen geldige OTR-sessie gevonden!</string> <string name="conversations_foreground_service">Conversations</string> - <string name="pref_keep_foreground_service">Hou service in voorgrond</string> + <string name="pref_keep_foreground_service">Dienst in voorgrond houden</string> <string name="pref_keep_foreground_service_summary">Belet het besturingssysteem van je verbinding te onderbreken</string> - <string name="choose_file">Kies bestand</string> + <string name="pref_export_logs">Exporteer logs</string> + <string name="pref_export_logs_summary">Schrijf logs naar SD-kaart</string> + <string name="notification_export_logs_title">Logs schrijven naar SD-kaart</string> + <string name="choose_file">Bestand kiezen</string> <string name="receiving_x_file">Ontvangen van %1$s (%2$d%% voltooid)</string> - <string name="download_x_file">Download %s</string> + <string name="download_x_file">%s downloaden</string> <string name="file">bestand</string> - <string name="open_x_file">Open %s</string> + <string name="open_x_file">%s openen</string> <string name="sending_file">versturen (%1$d%% voltooid)</string> <string name="preparing_file">Bestand klaarmaken voor versturen</string> <string name="x_file_offered_for_download">%s aangeboden om te downloaden</string> - <string name="cancel_transmission">Annuleer bestandsoverdracht</string> + <string name="cancel_transmission">Bestandsoverdracht annuleren</string> <string name="file_transmission_failed">bestandsoverdracht mislukt</string> <string name="file_deleted">Het bestand is verwijderd</string> <string name="no_application_found_to_open_file">Geen applicatie om bestand te openen</string> @@ -340,7 +364,7 @@ <string name="are_you_sure_verify_fingerprint">Ben je zeker dat je de OTR-vingerafdruk van je contact wil bevestigen?</string> <string name="pref_show_dynamic_tags">Toon dynamische tags</string> <string name="pref_show_dynamic_tags_summary">Toon enkel-lezen tags onder contacten</string> - <string name="enable_notifications">Meldingen aanzetten</string> + <string name="enable_notifications">Meldingen inschakelen</string> <string name="conference_with">Groepsgesprek aanmaken met…</string> <string name="no_conference_server_found">Geen groepsgespreksserver gevonden</string> <string name="conference_creation_failed">Aanmaken van groepsgesprek mislukt!</string> @@ -348,7 +372,18 @@ <string name="secret_accepted">Geheim aanvaard!</string> <string name="reset">Opnieuw instellen</string> <string name="account_image_description">Account-avatar</string> - <string name="copy_otr_clipboard_description">Kopieer OTR-vingerafdruk naar klembord</string> + <string name="copy_otr_clipboard_description">OTR-vingerafdruk kopiëren naar klembord</string> + <string name="copy_omemo_clipboard_description">OMEMO-vingerafdruk kopiëren naar klembord</string> + <string name="regenerate_omemo_key">OMEMO-sleutel opnieuw aanmaken</string> + <string name="wipe_omemo_pep">Andere apparaten van PEP verwijderen</string> + <string name="clear_other_devices">Apparaten wissen</string> + <string name="clear_other_devices_desc">Ben je zeker dat je alle andere apparaten van de OMEMO-aankondiging wil wissen? De volgende keer dat je apparaten verbinding maken zullen ze zich opnieuw aankondigen, maar zullen ze misschien niet de berichten ontvangen die intussen zijn verzonden.</string> + <string name="purge_key">Sleutel verwijderen</string> + <string name="purge_key_desc_part1">Ben je zeker dat je deze sleutel wil verwijderen?</string> + <string name="purge_key_desc_part2">Ze zal onherroepelijk beschouwd worden als gecompromitteerd en je zal er nooit meer een nieuwe sessie mee kunnen bouwen.</string> + <string name="error_no_keys_to_trust_server_error">Er zijn geen bruikbare sleutels beschikbaar voor dit contact.\nHet ophalen van nieuwe sleutels van de server is mislukt. Misschien is er iets mis met de server van je contact.</string> + <string name="error_no_keys_to_trust">Er zijn geen bruikbare sleutels beschikbaar voor dit contact. Als je zijn/haar sleutels hebt verwijderd dient hij/zij nieuwe aan te maken.</string> + <string name="error_trustkeys_title">Fout</string> <string name="fetching_history_from_server">Geschiedenis van server halen</string> <string name="no_more_history_on_server">Geen verdere geschiedenis op server</string> <string name="updating">Bijwerken…</string> @@ -365,8 +400,8 @@ <string name="current_password">Huidig wachtwoord</string> <string name="new_password">Nieuw wachtwoord</string> <string name="password_should_not_be_empty">Wachtwoord zou niet leeg mogen zijn</string> - <string name="enable_all_accounts">Alle accounts aanzetten</string> - <string name="disable_all_accounts">Alle accounts uitzetten</string> + <string name="enable_all_accounts">Alle accounts inschakelen</string> + <string name="disable_all_accounts">Alle accounts uitschakelen</string> <string name="perform_action_with">Actie uitvoeren met</string> <string name="no_affiliation">Geen aansluiting</string> <string name="no_role">Geen rol</string> @@ -377,7 +412,7 @@ <string name="remove_membership">Lidmaatschap verwijderen</string> <string name="grant_admin_privileges">Administratorprivileges verlenen</string> <string name="remove_admin_privileges">Administratorprivileges verwijderen</string> - <string name="remove_from_room">Verwijderen uit kamer</string> + <string name="remove_from_room">Verwijderen uit groepsgesprek</string> <string name="could_not_change_affiliation">Kon aansluiting niet wijzigen</string> <string name="ban_from_conference">Verbannen uit groepsgesprek</string> <string name="removing_from_public_conference">Je probeert %s te verwijderen uit een publiek groepsgesprek. De enige manier om dat te doen is door hem/haar permanent te verbannen.</string> @@ -386,8 +421,10 @@ <string name="public_conference">Publiek toegankelijk groepsgesprek</string> <string name="private_conference">Privé groepsgesprek, enkel toegankelijk voor leden</string> <string name="conference_options">Groepsgespreksopties</string> - <string name="members_only">Privé (alleen leden)</string> + <string name="members_only">Privé, enkel leden</string> <string name="non_anonymous">Niet anoniem</string> + <string name="moderated">Gemodereerd</string> + <string name="you_are_not_participating">Je neemt geen deel</string> <string name="modified_conference_options">Groepsgespreksopties aangepast!</string> <string name="could_not_modify_conference_options">Kon groepsgespreksopties niet aanpassen</string> <string name="never">Nooit</string> @@ -396,7 +433,7 @@ <string name="two_hours">2 uur</string> <string name="eight_hours">8 uur</string> <string name="until_further_notice">Voor onbepaalde duur</string> - <string name="pref_input_options">Input-opties</string> + <string name="pref_input_options">Invoer</string> <string name="pref_enter_is_send">Enter is versturen</string> <string name="pref_enter_is_send_summary">Gebruik de enter-toets om berichten te versturen</string> <string name="pref_display_enter_key">Toon enter-toets</string> @@ -408,33 +445,32 @@ <string name="apk">Android-applicatie</string> <string name="vcard">Contact</string> <string name="received_x_file">%s ontvangen</string> - <string name="disable_foreground_service">Voorgrond-service uitzetten</string> + <string name="disable_foreground_service">Voorgronddienst uitschakelen</string> <string name="touch_to_open_conversations">Raak aan om Conversations te openen</string> <string name="avatar_has_been_published">Avatar is gepubliceerd!</string> <string name="sending_x_file">Bezig met versturen van %s</string> <string name="offering_x_file">Bezig met aanbieden van %s</string> - <string name="hide_offline">Offline verbergen</string> - <string name="disable_account">Account uitzetten</string> - <string name="contact_is_typing">%s is aan het typen...</string> + <string name="hide_offline">Offline contacten verbergen</string> + <string name="disable_account">Account uitschakelen</string> + <string name="contact_is_typing">%s is aan het typen…</string> <string name="contact_has_stopped_typing">%s is gestopt met typen</string> - <string name="pref_chat_states">Type-meldingen</string> + <string name="pref_chat_states">Aan-het-typen-meldingen</string> <string name="pref_chat_states_summary">Laat je contacten weten wanneer je een nieuw bericht aan het schrijven bent</string> <string name="send_location">Locatie versturen</string> <string name="show_location">Locatie weergeven</string> - <string name="no_application_found_to_display_location">Geen applicatie gevonden om locatie weer te geven</string> + <string name="no_application_found_to_display_location">Geen applicatie om locatie weer te geven</string> <string name="location">Locatie</string> <string name="received_location">Locatie ontvangen</string> <string name="title_undo_swipe_out_conversation">Gesprek gesloten</string> <string name="title_undo_swipe_out_muc">Groepsgesprek verlaten</string> - <string name="pref_certificate_options">Certificaatopties</string> - <string name="pref_dont_trust_system_cas_title">Vertrouw geen systeem-CA\'s.</string> + <string name="pref_dont_trust_system_cas_title">Systeem-CA\'s niet vertrouwen</string> <string name="pref_dont_trust_system_cas_summary">Alle certificaten moeten handmatig goedgekeurd worden</string> - <string name="pref_remove_trusted_certificates_title">Verwijder certificaten</string> - <string name="pref_remove_trusted_certificates_summary">Verwijder handmatig goedgekeurde certificaten</string> + <string name="pref_remove_trusted_certificates_title">Certificaten verwijderen</string> + <string name="pref_remove_trusted_certificates_summary">Handmatig goedgekeurde certificaten verwijderen</string> <string name="toast_no_trusted_certs">Geen handmatig goedgekeurde certificaten</string> - <string name="dialog_manage_certs_title">Verwijder certificaten</string> - <string name="dialog_manage_certs_positivebutton">Verwijder selectie</string> - <string name="dialog_manage_certs_negativebutton">Annuleer</string> + <string name="dialog_manage_certs_title">Certificaten verwijderen</string> + <string name="dialog_manage_certs_positivebutton">Selectie verwijderen</string> + <string name="dialog_manage_certs_negativebutton">Annuleren</string> <plurals name="toast_delete_certificates"> <item quantity="one">%d certificaat verwijderd</item> <item quantity="other">%d certificaten verwijderd</item> @@ -447,6 +483,76 @@ <string name="pref_quick_action">Snelle actie</string> <string name="none">Geen</string> <string name="recently_used">Recent gebruikt</string> - <string name="choose_quick_action">Kies snelle actie</string> - <string name="file_not_found_on_remote_host">Bestand niet gevonden op externe server</string> + <string name="choose_quick_action">Snelle actie kiezen</string> + <string name="search_for_contacts_or_groups">Zoeken naar contacten of groepen</string> + <string name="send_private_message">Privébericht sturen</string> + <string name="user_has_left_conference">%s heeft het groepsgesprek verlaten!</string> + <string name="username">Gebruikersnaam</string> + <string name="username_hint">Gebruikersnaam</string> + <string name="invalid_username">Dit is geen geldige gebruikersnaam</string> + <string name="download_failed_server_not_found">Downloaden mislukt: server niet gevonden</string> + <string name="download_failed_file_not_found">Downloaden mislukt: bestand niet gevonden</string> + <string name="download_failed_could_not_connect">Downloaden mislukt: kon geen verbinding maken met host</string> + <string name="account_status_tor_unavailable">Tor-netwerk niet beschikbaar</string> + <string name="server_info_broken">Gebroken</string> + <string name="pref_presence_settings">Aanwezigheid</string> + <string name="pref_away_when_screen_off">Even weg wanneer scherm uit staat</string> + <string name="pref_away_when_screen_off_summary">Stelt je bron in als even weg wanneer het scherm uitgeschakeld is</string> + <string name="pref_xa_on_silent_mode">Niet beschikbaar in stille modus</string> + <string name="pref_xa_on_silent_mode_summary">Stelt je bron in als niet beschikbaar wanneer je apparaat in stille modus staat</string> + <string name="pref_show_connection_options">Uitgebreide verbindingsinstellingen</string> + <string name="pref_show_connection_options_summary">Toon hostnaam- en poortinstellingen bij instellen van een account</string> + <string name="hostname_example">xmpp.voorbeeld.be</string> + <string name="action_add_account_with_certificate">Account met certificaat toevoegen</string> + <string name="unable_to_parse_certificate">Kan certificaat niet verwerken</string> + <string name="authenticate_with_certificate">Laat leeg om te authenticeren met certificaat</string> + <string name="mam_prefs">Archiefvoorkeuren</string> + <string name="server_side_mam_prefs">Voorkeuren voor archief aan serverzijde</string> + <string name="fetching_mam_prefs">Ophalen van archiefvoorkeuren. Even geduld…</string> + <string name="unable_to_fetch_mam_prefs">Kon archiefvoorkeuren niet ophalen</string> + <string name="captcha_ocr">Captcha-tekst</string> + <string name="captcha_required">Captcha vereist</string> + <string name="captcha_hint">voer de tekst van de afbeelding in</string> + <string name="certificate_chain_is_not_trusted">Certificaatsketen is niet vertrouwd</string> + <string name="jid_does_not_match_certificate">Jabber-ID komt niet overeen met certificaat</string> + <string name="action_renew_certificate">Certificaat vernieuwen</string> + <string name="error_fetching_omemo_key">Fout bij ophalen van OMEMO-sleutel!</string> + <string name="verified_omemo_key_with_certificate">OMEMO-sleutel geverifieerd met certificaat!</string> + <string name="device_does_not_support_certificates">Je apparaat ondersteunt de selectie van cliënt-certificaten niet!</string> + <string name="pref_connection_options">Verbinding</string> + <string name="account_settings_hostname">Hostnaam</string> + <string name="account_settings_port">Poort</string> + <string name="not_a_valid_port">Dit is geen geldig poortnummer</string> + <string name="not_valid_hostname">Dit is geen geldige hostnaam</string> + <string name="connected_accounts">%1$d van %2$d accounts verbonden</string> + <plurals name="x_messages"> + <item quantity="one">%d bericht</item> + <item quantity="other">%d berichten</item> + </plurals> + <string name="shared_file_with_x">Bestand gedeeld met %s</string> + <string name="shared_image_with_x">Afbeelding gedeeld met %s</string> + <string name="no_storage_permission">Conversations heeft toegang nodig tot de externe opslag</string> + <string name="sync_with_contacts">Synchroniseer met contacten</string> + <string name="sync_with_contacts_long">Conversations wil je XMPP-rooster met je contacten vergelijken om hun volledige namen en profielfoto\'s te tonen.\n\nConversations zal je contacten enkel lokaal lezen en vergelijken zonder ze te uploaden naar je server.\n\nJe zal nu gevraagd worden Conversations toegang te verlenen tot je contacten.</string> + <string name="certificate_information">Certificaatinformatie</string> + <string name="certificate_subject">Onderwerp</string> + <string name="certificate_issuer">Uitgever</string> + <string name="certificate_cn">Algemene naam</string> + <string name="certificate_o">Organisatie</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(Niet beschikbaar)</string> + <string name="certificate_not_found">Geen certificaat gevonden</string> + <string name="notify_on_all_messages">Melding bij alle berichten</string> + <string name="notify_only_when_highlighted">Melding enkel wanneer vermeld</string> + <string name="notify_never">Meldingen uitgeschakeld</string> + <string name="notify_paused">Meldingen gepauzeerd</string> + <string name="always">Altijd</string> + <string name="automatically">Automatisch</string> + <string name="battery_optimizations_enabled">Batterij-optimalisaties ingeschakeld</string> + <string name="battery_optimizations_enabled_explained">Je apparaat voert sterke batterij-optimalisaties uit op Conversations, die kunnen leiden tot vertraagde meldingen of zelfs verlies van berichten.\nHet is aangeraden deze optimalisaties uit te schakelen.</string> + <string name="battery_optimizations_enabled_dialog">Je apparaat voert sterke batterij-optimalisaties uit op Conversations, die kunnen leiden tot vertraagde meldingen of zelfs verlies van berichten.\nJe zal nu gevraagd worden deze optimalisaties uit te schakelen.</string> + <string name="disable">Uitschakelen</string> + <string name="selection_too_large">Het gekozen vlak is te groot</string> + <string name="no_accounts">(Geen actieve accounts)</string> + <string name="this_field_is_required">Dit veld is vereist</string> </resources> diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index ea2ecc52..76d8db62 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Konwersacja szyfrowana</string> <string name="action_add_account">Dodaj konto</string> <string name="action_edit_contact">Edytuj nazwę</string> - <string name="action_add_phone_book">Dodaj do książki telefonicznej</string> + <string name="action_add_phone_book">Dodaj do kontaktów</string> <string name="action_delete_contact">Usuń z rostera</string> <string name="action_block_contact">Zablokuj kontakt</string> <string name="action_unblock_contact">Odblokuj kontakt</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d minut temu</string> <string name="unread_conversations">nieprzeczytanych konwersacji</string> <string name="sending">wysyłanie...</string> - <string name="encrypted_message">Deszyfrowanie wiadomości. Proszę czekać...</string> + <string name="message_decrypting">Odszyfrowywanie wiadomości. To zajmie tylko chwilę...</string> + <string name="pgp_message">Wiadomość zaszyfrowana OpenPGP</string> <string name="nick_in_use">Nazwa jest już w użyciu</string> <string name="admin">Admin</string> <string name="owner">Właściciel</string> @@ -65,7 +66,7 @@ <string name="problem_connecting_to_accounts">Nie można połączyć się z wieloma kontami</string> <string name="touch_to_fix">Dotknij tutaj aby zarządzać swoimi kontami</string> <string name="attach_file">Dołącz plik</string> - <string name="not_in_roster">Kontakt nie jest na twoim rosterze. Czy chcesz go dodać?</string> + <string name="not_in_roster">Kontakt nie należy do Twojej listy kontaktów. Czy chcesz go dodać?</string> <string name="add_contact">Dodaj kontakt</string> <string name="send_failed">wysyłanie nie powiodło się</string> <string name="send_rejected">odrzucono</string> @@ -74,25 +75,28 @@ <string name="clear_conversation_history">Wyczyść historię konwersacji</string> <string name="clear_histor_msg">Czy na pewno usunąć wszystkie wiadomości powiązane z konwersacją?\n\n<b>Uwaga:</b> Działanie nie wpływa na wiadomości przechowywane na innych urządzeniach lub serwerach.</string> <string name="delete_messages">Usuń wiadomości</string> - <string name="also_end_conversation">Zakończ konwersację po usunięciu historii</string> + <string name="also_end_conversation">po czym zakończ tę rozmowę</string> <string name="choose_presence">Wybierz widoczność dla kontaktu</string> - <string name="send_plain_text_message">Wyślij wiadomość jawną</string> + <string name="send_unencrypted_message">Wyślij wiadomość bez szyfrowania</string> <string name="send_otr_message">Wyślij zaszyfrowaną wiadomość (OTR)</string> + <string name="send_omemo_message">Wyślij wiadomość zaszyfrowaną OMEMO</string> + <string name="send_omemo_x509_message">Wyślij wiadomość zaszyfrowaną v\\OMEMO</string> <string name="send_pgp_message">Wyślij zaszyfrowaną wiadomość (OpenPGP)</string> - <string name="your_nick_has_been_changed">Twoja nazwa została zmieniona</string> + <string name="your_nick_has_been_changed">Twój nick został zmieniony</string> <string name="send_unencrypted">Wyślij bez szyfrowania</string> <string name="decryption_failed">Nie można odszyfrować. Sprawdź poprawność klucza prywatnego.</string> <string name="openkeychain_required">OpenKeychain</string> <string name="openkeychain_required_long">Conversations używa zewnętrznej aplikacji <b>OpenKeychain</b> do szyfrowania wiadomości i zarządzania kluczami publicznymi.\n\nOpenKeychain rozpowszechniany jest na licencji GPLv3 przez F-Droid lub Google Play.\n\n<small>(Zrestartuj Conversations po instalacji).</small></string> <string name="restart">Zrestartuj</string> <string name="install">Zainstaluj</string> + <string name="openkeychain_not_installed">Proszę zainstalować OpenKeychain</string> <string name="offering">oferowanie...</string> <string name="waiting">oczekiwanie...</string> <string name="no_pgp_key">Nie znaleziono klucza OpenPGP</string> <string name="contact_has_no_pgp_key">Conversations nie może zaszyfrować wiadomości, ponieważ kontakt nie udostępnia klucza publicznego.\n\n<small>Zasugeruj rozmówcy instalację OpenPGP.</small></string> <string name="no_pgp_keys">Nie znaleziono kluczy OpenPGP</string> <string name="contacts_have_no_pgp_keys">Conversations nie może zaszyfrować wiadomości, ponieważ kontakty nie udostępniają kluczy publicznych.\n\n<small>Zasugeruj rozmówcom instalację OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Otrzymano zaszyfrowaną wiadomość. Dotknij, aby odszyfrować i wyświetlić.</i></string> + <string name="encrypted_message_received"><i>Odebrano zaszyfrowaną wiadomość. Dotknij by odszyfrować</i></string> <string name="pref_general">Główne</string> <string name="pref_xmpp_resource">Zasób XMPP</string> <string name="pref_xmpp_resource_summary">Nazwa identyfikująca urządzenie</string> @@ -105,8 +109,6 @@ <string name="pref_vibrate_summary">Wibruj, gdy nadejdzie wiadomość</string> <string name="pref_sound">Dźwięk</string> <string name="pref_sound_summary">Odtwórz dźwięk z powiadomieniem</string> - <string name="pref_conference_notifications">Powiadomienia konferencji</string> - <string name="pref_conference_notifications_summary">Zawsze powiadamiaj o nowej wiadomości w konferencji</string> <string name="pref_notification_grace_period">Opóźnienie powiadomień</string> <string name="pref_notification_grace_period_summary">Wyłącz powiadomienia przez krótki czas po otrzymaniu kopii wiadomości</string> <string name="pref_advanced_options">Opcje zaawansowane</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">Serwer nie umożliwia rejestracji</string> <string name="account_status_security_error">Błąd zabezpieczeń</string> <string name="account_status_incompatible_server">Serwer niekompatybilny</string> - <string name="encryption_choice_none">Tekst jawny</string> + <string name="encryption_choice_unencrypted">Bez szyfrowania</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Edytuj konto</string> <string name="mgmt_account_delete">Usuń konto</string> <string name="mgmt_account_disable">Wyłącz tymczasowo</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">Hasła są niezgodne</string> <string name="invalid_jid">Wprowadzono niepoprawny Jabber ID</string> <string name="error_out_of_memory">Brak pamięci, obraz jest za duży</string> - <string name="add_phone_book_text">Czy chcesz dodać kontakt %s do książki telefonicznej?</string> + <string name="add_phone_book_text">Czy chcesz dodać %s do listy kontaktów?</string> <string name="contact_status_online">dostępny</string> <string name="contact_status_free_to_chat">chętny do rozmowy</string> <string name="contact_status_away">zaraz wracam</string> @@ -186,7 +189,8 @@ <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_pep">XEP-0163: PEP (Awatary / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: Przesyłanie plików przez HTTP</string> <string name="server_info_available">dostępny</string> <string name="server_info_unavailable">niedostępny</string> <string name="missing_public_keys">Brak informacji o kluczu publicznym</string> @@ -204,17 +208,28 @@ <string name="reception_failed">Odbiór nieudany</string> <string name="your_fingerprint">Twój odcisk klucza</string> <string name="otr_fingerprint">Odcisk klucza OTR</string> + <string name="omemo_fingerprint">Odcisk OMEMO</string> + <string name="omemo_fingerprint_x509">Odcisk v\\OMEMO</string> + <string name="omemo_fingerprint_selected_message">Odcisk OMEMO wiadomości</string> + <string name="omemo_fingerprint_x509_selected_message">Odcisk v\\OMEMO wiadomości</string> + <string name="this_device_omemo_fingerprint">Własny odcisk OMEMO</string> + <string name="other_devices">Pozostałe urządzenia</string> + <string name="trust_omemo_fingerprints">Zaufane odciski OMEMO</string> + <string name="fetching_keys">Pobieranie kluczy...</string> + <string name="done">Ukończono</string> <string name="verify">Weryfikuj</string> <string name="decrypt">Odszyfruj</string> <string name="conferences">Konferencje</string> <string name="search">Szukaj</string> <string name="create_contact">Utwórz kontakt</string> + <string name="enter_contact">Wpisz kontakt</string> <string name="join_conference">Dołącz do konferencji</string> <string name="delete_contact">Usuń kontakt</string> <string name="view_contact_details">Szczegóły kontaktu</string> <string name="block_contact">Zablokuj kontakt</string> <string name="unblock_contact">Odblokuj kontakt</string> <string name="create">Utwórz</string> + <string name="select">Wybierz</string> <string name="contact_already_exists">Kontakt już istnieje</string> <string name="join">Dołącz</string> <string name="conference_address">Adres konferencji</string> @@ -249,7 +264,6 @@ <string name="skip">Pomiń</string> <string name="disable_notifications">Wyłącz powiadomienia</string> <string name="disable_notifications_for_this_conversation">Wyłącz powiadomienia tej konwersacji</string> - <string name="notifications_disabled">Powiadomienia są wyłączone</string> <string name="enable">Włącz</string> <string name="conference_requires_password">Konferencja jest zabezpieczona hasłem</string> <string name="enter_password">Wprowadź hasło</string> @@ -284,15 +298,19 @@ <string name="pref_conference_name">Nazwa konferencji</string> <string name="pref_conference_name_summary">Nazywaj konferencję tematem zamiast Jabber ID</string> <string name="toast_message_otr_fingerprint">Odcisk klucza OTR został skopiowany do schowka</string> + <string name="toast_message_omemo_fingerprint">Odcisk klucza OMEMO został skopiowany do schowka!</string> <string name="conference_banned">Zbanowano cię w konferencji</string> <string name="conference_members_only">To jest zamknięty pokój</string> <string name="conference_kicked">Wyrzucono cię z konferencji</string> <string name="using_account">używając konta %s</string> + <string name="checking_x">Sprawdzanie %s na hoście HTTP</string> <string name="not_connected_try_again">Brak połączenia. Spróbuj ponownie później</string> + <string name="check_x_filesize">Sprawdź rozmiar %s</string> <string name="message_options">Opcje wiadomości</string> <string name="copy_text">Skopiuj tekst</string> <string name="copy_original_url">Skopiuj oryginalny URL</string> <string name="send_again">Wyślij ponownie</string> + <string name="file_url">URL pliku</string> <string name="message_text">Treść wiadomości</string> <string name="url_copied_to_clipboard">URL obrazu został skopiowany do schowka</string> <string name="message_copied_to_clipboard">Wiadomość została skopiowana do schowka</string> @@ -304,7 +322,6 @@ <string name="verify_otr">Weryfikuj OTR</string> <string name="remote_fingerprint">Zdalny odcisk klucza</string> <string name="scan">skanuj</string> - <string name="or_touch_phones">(lub zetknij telefony)</string> <string name="smp">Protokół socialist millionaire</string> <string name="shared_secret_hint">Podpowiedź lub pytanie</string> <string name="shared_secret_secret">Wspólny sekret</string> @@ -321,6 +338,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Usługa na pierwszym planie</string> <string name="pref_keep_foreground_service_summary">Uniemożliwia systemowi przerwanie połączenia</string> + <string name="pref_export_logs">Wyeksportuj historię rozmów</string> + <string name="pref_export_logs_summary">Zapisz historię na karcie SD</string> + <string name="notification_export_logs_title">Zapisywanie historii na karcie SD...</string> <string name="choose_file">Wybierz plik</string> <string name="receiving_x_file">Odbieranie %1$s (ukończono %2$d%%)</string> <string name="download_x_file">Pobierz %s</string> @@ -347,6 +367,17 @@ <string name="reset">Resetuj</string> <string name="account_image_description">Awatar konta</string> <string name="copy_otr_clipboard_description">Skopiuj odcisk klucza OTR do schowka</string> + <string name="copy_omemo_clipboard_description">Skopiuj odcisk klucza OMEMO do schowka</string> + <string name="regenerate_omemo_key">Wygeneruj ponownie klucz OMEMO</string> + <string name="wipe_omemo_pep">Usuń inne urządzenia z PEP</string> + <string name="clear_other_devices">Wyczyść urządzenia</string> + <string name="clear_other_devices_desc">Czy na pewno chcesz usunąć wszystkie inne urządzenia z ogłoszenia OMEMO? Następnym razem gdy połączą się Twoje urdzącenia, ogłoszą się one ponownie, ale mogą nie otrzymać wiadomości wysłanych w międzyczasie.</string> + <string name="purge_key">Skasuj klucz</string> + <string name="purge_key_desc_part1">Czy na pewno chcesz skasować usunąć odcisk klucza?</string> + <string name="purge_key_desc_part2">Zostanie bez odwołania uznane za zdradzone, i nigdy więcej nie będzie można stworzyć z nim sesji.</string> + <string name="error_no_keys_to_trust_server_error">Nie ma dostępnych kluczy dl atego kontaktu.\nPobieranie nowych kluczy z serwera nie powiodło się. Byćmoże jest coś nie tak z Twoim serwerem kontaktów?</string> + <string name="error_no_keys_to_trust">Nie ma dostępnych żadnych użytecznych kluczy dla tego kontaktu. Jeśli usunąłeś jakieś jego klucze, kontakt będzie musiał wygenerować nowe.</string> + <string name="error_trustkeys_title">Błąd</string> <string name="fetching_history_from_server">Pobieranie historii z serwera</string> <string name="no_more_history_on_server">Koniec historii na serwerze</string> <string name="updating">Aktualizowanie...</string> @@ -384,8 +415,10 @@ <string name="public_conference">Konferencja publiczna</string> <string name="private_conference">Konferencja prywatna, dla zaakceptowanych uczestników</string> <string name="conference_options">Opcje konferencji</string> - <string name="members_only">Prywatna (tylko zaakceptowani)</string> + <string name="members_only">Prywatne, tylko dla członków.</string> <string name="non_anonymous">Nieanonimowa</string> + <string name="moderated">Moderowany</string> + <string name="you_are_not_participating">Nie bierzesz udziału</string> <string name="modified_conference_options">Opcje konferencji zostały zmienione!</string> <string name="could_not_modify_conference_options">Nie udało się zmienić opcji konferencji</string> <string name="never">Nigdy</string> @@ -424,7 +457,6 @@ <string name="received_location">Otrzymano lokalizację</string> <string name="title_undo_swipe_out_conversation">Zamknięto konwersację</string> <string name="title_undo_swipe_out_muc">Opuszczono konferencję</string> - <string name="pref_certificate_options">Ustawienia certyfikatów</string> <string name="pref_dont_trust_system_cas_title">Nie ufaj certyfikatom systemowym</string> <string name="pref_dont_trust_system_cas_summary">Wymagaj ręcznego potwierdzania certyfikatów</string> <string name="pref_remove_trusted_certificates_title">Usuń certyfikat</string> @@ -448,4 +480,65 @@ <string name="none">Brak</string> <string name="recently_used">Ostatnio używana</string> <string name="choose_quick_action">Wybierz szybką akcję</string> + <string name="search_for_contacts_or_groups">Szukaj kontaktów i grup</string> + <string name="send_private_message">Wyślij wiadomość prywatną</string> + <string name="user_has_left_conference">%s opuścił(a) konferencję!</string> + <string name="username">Nazwa użytkownika</string> + <string name="username_hint">Nazwa użytkownika</string> + <string name="invalid_username">Błędna nazwa użytkownika</string> + <string name="download_failed_server_not_found">Pobieranie nieudane: Nie odnaleziono serwera</string> + <string name="download_failed_file_not_found">Pobieranie nieudane: Nie odnaleziono pliku</string> + <string name="download_failed_could_not_connect">Pobieranie nieudane: Nie można połączyć z hostem</string> + <string name="account_status_tor_unavailable">Sieć TOR jest niedostepna</string> + <string name="server_info_broken">Zepsute</string> + <string name="pref_away_when_screen_off">Status \"Oddalony\" gdy wyświetlacz jest wyłączony</string> + <string name="pref_away_when_screen_off_summary">Oznacza Twój zasób jako \"Oddalony\", gdy wyświetlacz jest wyłączony</string> + <string name="pref_xa_on_silent_mode">Niedostepne w trybie cichym</string> + <string name="pref_xa_on_silent_mode_summary">Oznacza Twój zasób jako \"Nieobecny\" gdy urządzenie jest w trybie cichym</string> + <string name="action_add_account_with_certificate">Dodaj konto za pomocą certyfikatu</string> + <string name="unable_to_parse_certificate">Nie mogę odczytać certyfikatu</string> + <string name="authenticate_with_certificate">Pozostaw puste by autoryzować za pomocą certyfikatu</string> + <string name="captcha_ocr">Captcha</string> + <string name="captcha_required">Captcha wymagana</string> + <string name="captcha_hint">przepisz tekst z obrazka</string> + <string name="certificate_chain_is_not_trusted">Łańcuch certyfikatu nie jest zaufany</string> + <string name="jid_does_not_match_certificate">Jabber ID nie odpowiada certyfikatowi</string> + <string name="action_renew_certificate">Odnów certyfikat</string> + <string name="error_fetching_omemo_key">Błąd pobierania klucza OMEMO!</string> + <string name="verified_omemo_key_with_certificate">Zweryfikowano klucz OMEMO z certyfikatem</string> + <string name="device_does_not_support_certificates">Twoje urządzenie nie wspiera wyboru certyfikatów klienckich</string> + <string name="account_settings_hostname">Nazwa hosta</string> + <string name="account_settings_port">Port</string> + <string name="not_a_valid_port">To nie jest prawidłowy numer portu</string> + <string name="not_valid_hostname">To nie jest prawidłowa nazwa hosta</string> + <string name="connected_accounts">%1$d z %2$d kont połączonych</string> + <plurals name="x_messages"> + <item quantity="one">%d wiadomość</item> + <item quantity="few">%d wiadomości</item> + <item quantity="other">%d wiadomości</item> + </plurals> + <string name="shared_file_with_x">Dzielony plik z %s</string> + <string name="shared_image_with_x">Dzielony obraz z %s</string> + <string name="no_storage_permission">Conversations potrzebuje dostęp do zewnętrznego magazynu</string> + <string name="sync_with_contacts">Synchronizuj z kontaktami</string> + <string name="sync_with_contacts_long">Conversations chce dopasować Twoje kontakty XMPP z listą kontaktów w telefonie, by uzupełnić ich pełne imiona oraz awatary.\nConversations jedynie przeczyta Twoje kontakty i dopasuje je lokalnie, bez wysyłania na Twój serwer.\n\nZostaniesz teraz poproszony o przydzielenie pozwolenia na odczyt Twoich kontaktów.</string> + <string name="certificate_information">Informacja o certyfikacie</string> + <string name="certificate_subject">Temat</string> + <string name="certificate_issuer">Wystawca</string> + <string name="certificate_cn">Nazwa</string> + <string name="certificate_o">Organizacja</string> + <string name="certicate_info_not_available">(Niedostępne)</string> + <string name="certificate_not_found">Nie znaleziono certyfikatu</string> + <string name="notify_on_all_messages">Powiadom o wszystkich wiadomościach</string> + <string name="notify_only_when_highlighted">Powiadom tylko gdy wspomniano</string> + <string name="notify_never">Powiadomienia wyłączone</string> + <string name="notify_paused">Powiadomienia wstrzymane</string> + <string name="pref_picture_compression">Kompresuj obrazki</string> + <string name="always">Zawsze</string> + <string name="automatically">Automatycznie</string> + <string name="battery_optimizations_enabled">Optymalizacje zużycia baterii włączone</string> + <string name="battery_optimizations_enabled_explained">Twoje urządzenie wykonuje poważnie optymalizacje zużycia baterii przez Conversations, które mogą powodować opóźnienie powiadomień lub nawet utratę wiadomości.\nZaleca się ich wyłączenie.</string> + <string name="battery_optimizations_enabled_dialog">Twoje urządzenie wykonuje poważnie optymalizacje zużycia baterii przez Conversations, które mogą powodować opóźnienie powiadomień lub nawet utratę wiadomości.\nZostaniesz teraz poproszony o ich wyłączenie</string> + <string name="disable">Wyłącz</string> + <string name="selection_too_large">Zaznaczony obszar jest zbyt duży</string> </resources> diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 00000000..0b8b0080 --- /dev/null +++ b/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,562 @@ +<?xml version='1.0' encoding='UTF-8'?> +<resources> + <string name="action_settings">Configurações</string> + <string name="action_add">Nova conversa</string> + <string name="action_accounts">Gerenciar contas</string> + <string name="action_end_conversation">Encerrar essa conversa</string> + <string name="action_contact_details">Detalhes do contato</string> + <string name="action_muc_details">Detalhes da conferência</string> + <string name="action_secure">Conversa segura</string> + <string name="action_add_account">Adicionar conta</string> + <string name="action_edit_contact">Editar o nome</string> + <string name="action_add_phone_book">Adicionar ao livro de endereços</string> + <string name="action_delete_contact">Excluir da lista de contatos</string> + <string name="action_block_contact">Bloquear contato</string> + <string name="action_unblock_contact">Desbloquear contato</string> + <string name="action_block_domain">Bloquear domínio</string> + <string name="action_unblock_domain">Desbloquear domínio</string> + <string name="title_activity_manage_accounts">Gerenciar contas</string> + <string name="title_activity_settings">Configurações</string> + <string name="title_activity_conference_details">Detalhes da conferência</string> + <string name="title_activity_contact_details">Detalhes do contato</string> + <string name="title_activity_sharewith">Compartilhar com a conversa</string> + <string name="title_activity_start_conversation">Conversar</string> + <string name="title_activity_choose_contact">Selecione o contato</string> + <string name="title_activity_block_list">Lista de bloqueios</string> + <string name="just_now">agora</string> + <string name="minute_ago">1 minuto atrás</string> + <string name="minutes_ago">%d minutos atrás</string> + <string name="unread_conversations">conversas não lidas</string> + <string name="sending">enviando...</string> + <string name="message_decrypting">Descriptografando a mensagem. Por favor, aguarde...</string> + <string name="pgp_message">Mensagem criptografada via OpenPGP</string> + <string name="nick_in_use">Este apelido já está em uso</string> + <string name="admin">Administrador</string> + <string name="owner">Dono</string> + <string name="moderator">Moderador</string> + <string name="participant">Participante</string> + <string name="visitor">Visitante</string> + <string name="remove_contact_text">Deseja remover %s da sua lista de contatos? As conversas associadas a esse contato não serão removidas.</string> + <string name="block_contact_text">Deseja bloquear o recebimento de mensagens de %s?</string> + <string name="unblock_contact_text">Deseja desbloquear o recebimento de mensagens de %s?</string> + <string name="block_domain_text">Bloquear todos os contatos de %s?</string> + <string name="unblock_domain_text">Desbloquear todos os contatos de %s?</string> + <string name="contact_blocked">Contato bloqueado</string> + <string name="remove_bookmark_text">Você deseja remover %s dos favoritos? As conversas associadas a esse favorito não serão removidas.</string> + <string name="register_account">Registrar uma nova conta no servidor</string> + <string name="change_password_on_server">Alterar a senha no servidor</string> + <string name="share_with">Compartilhar com...</string> + <string name="start_conversation">Conversar</string> + <string name="invite_contact">Convidar um contato</string> + <string name="contacts">Contatos</string> + <string name="cancel">Cancelar</string> + <string name="set">Definir</string> + <string name="add">Adicionar</string> + <string name="edit">Editar</string> + <string name="delete">Excluir</string> + <string name="block">Bloquear</string> + <string name="unblock">Desbloquear</string> + <string name="save">Salvar</string> + <string name="ok">Ok</string> + <string name="crash_report_title">Ocorreu um erro no Conversations</string> + <string name="crash_report_message">Ao enviar os stack traces você está colaborando com o desenvolvimento do Conversations\n<b>Atenção:</b> Isso usará a sua conta XMPP para enviar o stack trace para o desenvolvedor.</string> + <string name="send_now">Enviar agora</string> + <string name="send_never">Não pergunte novamente</string> + <string name="problem_connecting_to_account">Não foi possível se conectar à conta</string> + <string name="problem_connecting_to_accounts">Não foi possível se conectar a múltiplas contas</string> + <string name="touch_to_fix">Toque aqui para gerenciar suas contas</string> + <string name="attach_file">Anexar arquivo</string> + <string name="not_in_roster">Essa pessoa não está na sua lista de contatos. Deseja adicioná-la?</string> + <string name="add_contact">Adicionar contato</string> + <string name="send_failed">não foi entregue</string> + <string name="send_rejected">rejeitada</string> + <string name="preparing_image">Preparando a imagem para transmissão</string> + <string name="action_clear_history">Limpar o histórico</string> + <string name="clear_conversation_history">Limpa o histórico de conversas</string> + <string name="clear_histor_msg">Deseja excluir todas as mensagens dessa conversa?\n\n<b>Atenção:</b> Isso não afetará mensagens armazenadas em outros dispositivos ou servidores.</string> + <string name="delete_messages">Excluir mensagens</string> + <string name="also_end_conversation">Encerrar essa conversa em seguida</string> + <string name="choose_presence">Selecione a presença para esse contato</string> + <string name="send_unencrypted_message">Enviar mensagem não criptografada</string> + <string name="send_otr_message">Enviar mensagem criptografada via OTR</string> + <string name="send_omemo_message">Enviar mensagem criptografada via OMEMO</string> + <string name="send_omemo_x509_message">Enviar mensagem criptografada via v\\OMEMO</string> + <string name="send_pgp_message">Enviar mensagem criptografada via OpenPGP</string> + <string name="your_nick_has_been_changed">Seu apelido foi alterado</string> + <string name="send_unencrypted">Enviar descriptografada</string> + <string name="decryption_failed">Não foi possível descriptografar. Talvez você não tenha a chave privada apropriada.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">O Conversations utiliza um aplicativo de terceiros chamado <b>OpenKeychain</b> para criptografar e descriptografar as mensagens e gerenciar suas chaves públicas.\n\nO OpenKeychain é licenciado sob a GPLv3 e está disponível no F-Droid e Google Play.\n\n<small>(Por favor reinicie o Conversations em seguida)</small></string> + <string name="restart">Reiniciar</string> + <string name="install">Instalar</string> + <string name="openkeychain_not_installed">Por favor, instale o OpenKeychain</string> + <string name="offering">oferecendo...</string> + <string name="waiting">aguardando...</string> + <string name="no_pgp_key">Não foi encontrada nenhuma chave OpenPGP</string> + <string name="contact_has_no_pgp_key">O Conversations não conseguiu criptografar suas mensagens porque o seu contato não está anunciando a chave pública dele(a).\n\n<small>Por favor, solicite ao seu contato para configurar o OpenPGP.</small></string> + <string name="no_pgp_keys">Não foi encontrada nenhuma chave OpenPGP</string> + <string name="contacts_have_no_pgp_keys">O Conversations não conseguiu criptografar suas mensagens porque os seus contatos não estão anunciando a chave pública deles(as).\n\n<small>Por favor, solicite aos seus contatos que configurem o OpenPGP.</small></string> + <string name="encrypted_message_received"><i>Foi recebida uma mensagem criptografada. Toque para descriptografar.</i></string> + <string name="pref_general">Geral</string> + <string name="pref_xmpp_resource">Recurso XMPP</string> + <string name="pref_xmpp_resource_summary">O nome pelo qual esse cliente se identifica</string> + <string name="pref_accept_files">Aceitar arquivos</string> + <string name="pref_accept_files_summary">Aceitar automaticamente arquivos menores que...</string> + <string name="pref_notification_settings">Notificação</string> + <string name="pref_notifications">Notificações</string> + <string name="pref_notifications_summary">Notificar quando uma nova mensagem for recebida</string> + <string name="pref_vibrate">Vibração</string> + <string name="pref_vibrate_summary">Vibrar quando uma nova mensagem for recebida</string> + <string name="pref_sound">Som</string> + <string name="pref_sound_summary">Som de notificação</string> + <string name="pref_notification_grace_period">Tempo de espera da notificação</string> + <string name="pref_notification_grace_period_summary">Desativar notificações por um curto período após uma copia ser recebida</string> + <string name="pref_advanced_options">Avançado</string> + <string name="pref_never_send_crash">Nunca enviar relatórios de erros</string> + <string name="pref_never_send_crash_summary">Ao enviar os stack traces você está colaborando com o desenvolvimento do Conversations</string> + <string name="pref_confirm_messages">Confirmação de mensangens</string> + <string name="pref_confirm_messages_summary">Permitir que um contato saiba quando você recebeu e leu uma mensagem</string> + <string name="pref_ui_options">IU</string> + <string name="openpgp_error">O OpenKeychain informou um erro</string> + <string name="error_decrypting_file">Ocorreu um erro de E/S na descriptografia o arquivo</string> + <string name="accept">Aceitar</string> + <string name="error">Ocorreu um erro</string> + <string name="pref_grant_presence_updates">Permitir atualizações de presença</string> + <string name="pref_grant_presence_updates_summary">Permitir antecipadamente e solicitar por atualizações de presença aos contatos que você criar</string> + <string name="subscriptions">Inscrições</string> + <string name="your_account">Sua conta</string> + <string name="keys">Chaves</string> + <string name="send_presence_updates">Enviar atualizações de presença</string> + <string name="receive_presence_updates">Receber atualizações de presença</string> + <string name="ask_for_presence_updates">Pedir por atualizações de presença</string> + <string name="attach_choose_picture">Selecionar uma imagem</string> + <string name="attach_take_picture">Tirar uma foto</string> + <string name="preemptively_grant">Autorizar antecipadamente as solicitações de inscrição</string> + <string name="error_not_an_image_file">O arquivo selecionado não é uma imagem</string> + <string name="error_compressing_image">Ocorreu um erro durante a conversão do arquivo de imagem</string> + <string name="error_file_not_found">Arquivo não encontrado</string> + <string name="error_io_exception">Ocorreu um erro genérico de E/S. Você tem espaço de armazenamento suficiente no seu aparelho?</string> + <string name="error_security_exception_during_image_copy">O aplicativo que você usou para selecionar esta imagem não nos forneceu permissões suficientes para ler o arquivo.\n\n<small>Utilize um gerenciador de arquivos diferente para selecionar a imagem.</small></string> + <string name="account_status_unknown">Desconhecido</string> + <string name="account_status_disabled">Temporariamente desabilitado</string> + <string name="account_status_online">Conectado</string> + <string name="account_status_connecting">Conectando\u2026</string> + <string name="account_status_offline">Desconectado</string> + <string name="account_status_unauthorized">Não autorizado</string> + <string name="account_status_not_found">Servidor não encontrado</string> + <string name="account_status_no_internet">Sem conectividade</string> + <string name="account_status_regis_fail">Não foi possível efetuar o registro</string> + <string name="account_status_regis_conflict">Esse nome de usuário já está em uso</string> + <string name="account_status_regis_success">Registro efetuado com sucesso</string> + <string name="account_status_regis_not_sup">O servidor não aceita o registro</string> + <string name="account_status_security_error">Erro de segurança</string> + <string name="account_status_incompatible_server">Servidor incompatível</string> + <string name="encryption_choice_unencrypted">Descriptografada</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Editar a conta</string> + <string name="mgmt_account_delete">Excluir a conta</string> + <string name="mgmt_account_disable">Desabilitar temporariamente</string> + <string name="mgmt_account_publish_avatar">Publicar o avatar</string> + <string name="mgmt_account_publish_pgp">Publicar a chave pública OpenPGP</string> + <string name="mgmt_account_enable">Habilitar a conta</string> + <string name="mgmt_account_are_you_sure">Tem certeza?</string> + <string name="mgmt_account_delete_confirm_text">Se você excluir a sua conta todo o seu histórico de conversas será perdido</string> + <string name="attach_record_voice">Gravar voz</string> + <string name="account_settings_jabber_id">ID Jabber</string> + <string name="account_settings_password">Senha</string> + <string name="account_settings_example_jabber_id">nomedeusuario@exemplo.com</string> + <string name="account_settings_confirm_password">Confirme a senha</string> + <string name="password">Senha</string> + <string name="confirm_password">Confirme a senha</string> + <string name="passwords_do_not_match">As senhas não coincidem</string> + <string name="invalid_jid">Esse não é um ID Jabber válido</string> + <string name="error_out_of_memory">Memória insuficiente. A imagem é muito grande</string> + <string name="add_phone_book_text">Deseja adicionar %s ao seu livro de endereços?</string> + <string name="contact_status_online">conectado</string> + <string name="contact_status_free_to_chat">disponível para conversar</string> + <string name="contact_status_away">afastado</string> + <string name="contact_status_extended_away">indisponível</string> + <string name="contact_status_do_not_disturb">não pertube</string> + <string name="contact_status_offline">desconectado</string> + <string name="muc_details_conference">Conferência</string> + <string name="muc_details_other_members">Outros membros</string> + <string name="server_info_show_more">Informações do servidor</string> + <string name="server_info_mam">XEP-0313: MAM</string> + <string name="server_info_carbon_messages">XEP-0280: Cópias das mensagens</string> + <string name="server_info_csi">XEP-0352: Indicação do status do cliente</string> + <string name="server_info_blocking">XEP-0191: Comando de bloqueio</string> + <string name="server_info_roster_version">XEP-0237: Versionamento da lista de contatos</string> + <string name="server_info_stream_management">XEP-0198: Gerenciamento de fluxo</string> + <string name="server_info_pep">XEP-0163: PEP (Avatares / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: Envio de arquivos via HTTP</string> + <string name="server_info_push">XEP-0357: Push</string> + <string name="server_info_available">disponível</string> + <string name="server_info_unavailable">indisponível</string> + <string name="missing_public_keys">Avisos de ausência de chave pública</string> + <string name="last_seen_now">visto agora</string> + <string name="last_seen_min">visto há 1 minuto atrás</string> + <string name="last_seen_mins">visto há %d minutos atrás</string> + <string name="last_seen_hour">visto há 1 hora atrás</string> + <string name="last_seen_hours">visto há %d horas atrás</string> + <string name="last_seen_day">visto há 1 dia atrás</string> + <string name="last_seen_days">visto há %d dias atrás</string> + <string name="never_seen">nunca visto</string> + <string name="install_openkeychain">Mensagem criptografada. Por favor instale o OpenKeychain para descriptografar.</string> + <string name="unknown_otr_fingerprint">Impressão digital OTR desconhecida</string> + <string name="openpgp_messages_found">Foram encontradas mensagens criptografadas com OpenPGP</string> + <string name="reception_failed">A recepção falhou</string> + <string name="your_fingerprint">Sua impressão digital</string> + <string name="otr_fingerprint">Impressão digital OTR</string> + <string name="omemo_fingerprint">Impressão digital OMEMO</string> + <string name="omemo_fingerprint_x509">Impressão digital v\\OMEMO</string> + <string name="omemo_fingerprint_selected_message">Impressão digital OMEMO da mensagem</string> + <string name="omemo_fingerprint_x509_selected_message">Impressão digital v\\OMEMO da mensagem</string> + <string name="this_device_omemo_fingerprint">Impressão digital OMEMO própria</string> + <string name="other_devices">Outros dispositivos</string> + <string name="trust_omemo_fingerprints">Confiar nas impressões digitais OMEMO</string> + <string name="fetching_keys">Buscando as chaves...</string> + <string name="done">Concluído</string> + <string name="verify">Verificar</string> + <string name="decrypt">Descriptografar</string> + <string name="conferences">Conferências</string> + <string name="search">Pesquisar</string> + <string name="create_contact">Criar contato</string> + <string name="enter_contact">Digite o contato</string> + <string name="join_conference">Entrar na conferência</string> + <string name="delete_contact">Excluir o contato</string> + <string name="view_contact_details">Ver os detalhes do contato</string> + <string name="block_contact">Bloquear o contato</string> + <string name="unblock_contact">Desbloquear o contato</string> + <string name="create">Criar</string> + <string name="select">Selecionar</string> + <string name="contact_already_exists">Esse contato já existe</string> + <string name="join">Entrar</string> + <string name="conference_address">Endereço da conferência</string> + <string name="conference_address_example">sala@conferencia.example.com</string> + <string name="save_as_bookmark">Salvar como favorito</string> + <string name="delete_bookmark">Excluir o favorito</string> + <string name="bookmark_already_exists">Esse favorito já existe</string> + <string name="you">Você</string> + <string name="action_edit_subject">Editar o assunto da conferência</string> + <string name="conference_not_found">A conferência não foi encontrada</string> + <string name="conference_unknown_error">Foi recebido um erro desconhecido</string> + <string name="leave">Sair</string> + <string name="contact_added_you">O contato foi adicionado à sua lista de contatos</string> + <string name="add_back">Adicionar novamente</string> + <string name="contact_has_read_up_to_this_point">%s leu até esse ponto</string> + <string name="publish">Publicar</string> + <string name="touch_to_choose_picture">Toque o avatar para selecionar uma imagem da galeria</string> + <string name="publish_avatar_explanation">Por favor observe: todos aqueles que acompanham suas atualizações de presença poderão ver essa imagem</string> + <string name="publishing">Publicando...</string> + <string name="error_publish_avatar_server_reject">O servidor rejeitou sua publicação</string> + <string name="error_publish_avatar_converting">Ocorreu um erro durante a conversão da sua imagem</string> + <string name="error_saving_avatar">Não foi possível salvar o avatar no disco</string> + <string name="or_long_press_for_default">(Ou mantenha pressionado para voltar ao padrão)</string> + <string name="error_publish_avatar_no_server_support">O seu servidor não suporta a publicação de avatares</string> + <string name="private_message">sussurado</string> + <string name="private_message_to">para %s</string> + <string name="send_private_message_to">Enviar mensagem privada para %s</string> + <string name="connect">Conectar</string> + <string name="account_already_exists">Essa conta já existe</string> + <string name="next">Próximo</string> + <string name="server_info_session_established">Sessão atual estebelecida</string> + <string name="additional_information">Informação adicional</string> + <string name="skip">Pular</string> + <string name="disable_notifications">Desabilitar notificações</string> + <string name="disable_notifications_for_this_conversation">Desabilitar notificações para essa conversa</string> + <string name="enable">Habilitar</string> + <string name="conference_requires_password">Essa conferencia requer uma senha</string> + <string name="enter_password">Digite a senha</string> + <string name="missing_presence_updates">Sem atualizações de presença do contato</string> + <string name="request_presence_updates">Por favor, primeiro solicite atualizações de presençado seu contato.\n\n<small>Isso determinará qual(is) cliente(s) o seu contato está usando.</small></string> + <string name="request_now">Solicitar agora</string> + <string name="delete_fingerprint">Excluir impressão digital</string> + <string name="sure_delete_fingerprint">Tem certeza que deseja remover essa impressão digital?</string> + <string name="ignore">Ignorar</string> + <string name="without_mutual_presence_updates"><b>Aviso:</b> Enviar isso sem atualizações mútuas de presença pode provocar problemas inesperados.\n\n<small>Verifique nos detalhes do contato suas inscrições de presença.</small></string> + <string name="pref_security_settings">Segurança</string> + <string name="pref_force_encryption">Forçar criptografia ponto-a-ponto</string> + <string name="pref_force_encryption_summary">Sempre envie mensagem criptografada (exceto para conferências)</string> + <string name="pref_allow_message_correction">Permitir a correção da mensagem</string> + <string name="pref_allow_message_correction_summary">Permite que seus contatos edite suas mensagens retroativamente</string> + <string name="pref_dont_save_encrypted">Não salve as mensagens criptografadas</string> + <string name="pref_dont_save_encrypted_summary">Atenção: Isso pode levar a perda de mensagens</string> + <string name="pref_expert_options">Configurações avançadas</string> + <string name="pref_expert_options_summary">Por favor, use com cuidado</string> + <string name="title_activity_about">Sobre o Conversations</string> + <string name="pref_about_conversations_summary">Informações de licenciamento e construção</string> + <string name="title_pref_quiet_hours">Horário de sossego</string> + <string name="title_pref_quiet_hours_start_time">Início</string> + <string name="title_pref_quiet_hours_end_time">Fim</string> + <string name="title_pref_enable_quiet_hours">Habilitar horário de sossego</string> + <string name="pref_quiet_hours_summary">As notificações serão silenciadas no horário de sossego</string> + <string name="pref_use_larger_font">Aumentar o tamanho da fonte</string> + <string name="pref_use_larger_font_summary">Usar fontes maiores por todo aplicativo</string> + <string name="pref_use_send_button_to_indicate_status">O botão de envio indica o status</string> + <string name="pref_use_indicate_received">Solicitar confirmação de recebimento</string> + <string name="pref_use_indicate_received_summary">As mensagens recebidas serão marcadas com um tique verde, se suportado</string> + <string name="pref_use_send_button_to_indicate_status_summary">Colorir o botão de envio para indicar o status do contato</string> + <string name="pref_expert_options_other">Outras</string> + <string name="pref_conference_name">Nome da conferência</string> + <string name="pref_conference_name_summary">Use o assunto da sala ao invés do JID para identificar as conferências</string> + <string name="pref_autojoin">Entrar automaticamente nas conferências</string> + <string name="pref_autojoin_summary">Respeitar a opção de entrada automática dos marcadores de conferência</string> + <string name="toast_message_otr_fingerprint">Impressão digital OTR copiada para a área de transferência!</string> + <string name="toast_message_omemo_fingerprint">Impressão digital OMEMO copiada para a área de transferência!</string> + <string name="conference_banned">Você foi banido dessa conferência</string> + <string name="conference_members_only">Essa conferência é restrita a seus membros</string> + <string name="conference_kicked">Você foi expulso dessa conferência</string> + <string name="using_account">usando a conta %s</string> + <string name="checking_x">Verificando %s no host HTTP</string> + <string name="not_connected_try_again">Você não está conectado. Tente novamente mais tarde.</string> + <string name="check_x_filesize">Verificar o tamanho de %s</string> + <string name="message_options">Opções da mensagem</string> + <string name="copy_text">Copiar o texto</string> + <string name="copy_original_url">Copiar a URL original</string> + <string name="send_again">Enviar novamente</string> + <string name="file_url">URL do arquivo</string> + <string name="message_text">Texto da mensagem</string> + <string name="url_copied_to_clipboard">A URL foi copiada para a área de transferência</string> + <string name="message_copied_to_clipboard">A mensagem foi copiada para a área de transferência</string> + <string name="image_transmission_failed">Não foi possível enviar a imagem</string> + <string name="scan_qr_code">Escanear QR code</string> + <string name="show_qr_code">Exibir QR code</string> + <string name="show_block_list">Exibir a lista de bloqueios</string> + <string name="account_details">Detalhes da conta</string> + <string name="verify_otr">Verificar OTR</string> + <string name="remote_fingerprint">Impressão digital remota</string> + <string name="scan">escanear</string> + <string name="smp">Socialist Millionaire Protocol</string> + <string name="shared_secret_hint">Dica ou pergunta</string> + <string name="shared_secret_secret">Segredo compartilhado</string> + <string name="confirm">Confirmar</string> + <string name="in_progress">Em progresso</string> + <string name="respond">Responder</string> + <string name="failed">Erro</string> + <string name="secrets_do_not_match">Os segredos não correspondem</string> + <string name="try_again">Tentar novamente</string> + <string name="finish">Finalizar</string> + <string name="verified">Verificado!</string> + <string name="smp_requested">O contato solicitou uma verificação SMP</string> + <string name="no_otr_session_found">Não foi encontrada nenhuma sessão OTR válida!</string> + <string name="conversations_foreground_service">Conversations</string> + <string name="pref_keep_foreground_service">Manter o serviço em segundo plano</string> + <string name="pref_keep_foreground_service_summary">Impede que o sistema operacional encerre sua conexão</string> + <string name="pref_export_logs">Exportar registros</string> + <string name="pref_export_logs_summary">Escrever os registros no cartão SD</string> + <string name="notification_export_logs_title">Escrevendo os registros no cartão SD</string> + <string name="choose_file">Selecione o arquivo</string> + <string name="receiving_x_file">Recebendo %1$s (completou %2$d%%)</string> + <string name="download_x_file">Baixar %s</string> + <string name="file">arquivo</string> + <string name="open_x_file">Abrir %s</string> + <string name="sending_file">enviando (completou %1$d%%)</string> + <string name="preparing_file">Preparando o arquivo para o envio</string> + <string name="x_file_offered_for_download">%s oferecido para baixar</string> + <string name="cancel_transmission">Cancelar transmissão</string> + <string name="file_transmission_failed">não foi possível transferir o arquivo</string> + <string name="file_deleted">O arquivo foi excluído</string> + <string name="no_application_found_to_open_file">Não foi encontrado nenhum aplicativo para abrir o arquivo</string> + <string name="could_not_verify_fingerprint">Não foi possível verificar a impressão digital</string> + <string name="manually_verify">Verificado manualmente</string> + <string name="are_you_sure_verify_fingerprint">Deseja realmente verificar as impressões digitais OTR dos seus contatos?</string> + <string name="pref_show_dynamic_tags">Exibir etiquetas dinâmicas</string> + <string name="pref_show_dynamic_tags_summary">Exibir etiquetas somente de leitura abaixo dos contatos</string> + <string name="enable_notifications">Habilitar notificações</string> + <string name="conference_with">Criar conferência com...</string> + <string name="no_conference_server_found">Não foi encontrado nenhum servidor de conferências</string> + <string name="conference_creation_failed">Não foi possível criar a conferência!</string> + <string name="conference_created">A conferência foi criada!</string> + <string name="secret_accepted">O segredo foi aceito!</string> + <string name="reset">Redefinir</string> + <string name="account_image_description">Avatar da conta</string> + <string name="copy_otr_clipboard_description">Copiar a impressão digital OTR para a área de transferência</string> + <string name="copy_omemo_clipboard_description">Copiar a impressão digital OMEMO para a área de transferência</string> + <string name="regenerate_omemo_key">Regerar a chave OMEMO</string> + <string name="wipe_omemo_pep">Remover outros dispositivos do PEP</string> + <string name="clear_other_devices">Remover dispositivos</string> + <string name="clear_other_devices_desc">Deseja realmente remover todos os outros dispositivos dos anúncios OMEMO? Na próxima vez que seus dispositivos conectarem eles se reanunciarão, entretanto, eles podem não receber mensagens nesse intervalo de tempo.</string> + <string name="purge_key">Expurgar a chave</string> + <string name="purge_key_desc_part1">Deseja realmente expurgar essa chave?</string> + <string name="purge_key_desc_part2">Ela será considerada irreversivelmente comprometida e você nunca mais conseguirá estabelecer uma sessão com ela.</string> + <string name="error_no_keys_to_trust_server_error">Não existe nenhuma chave utilizável para esse contato.\nNão foi possível obter nenhuma chave nova do servidor. Pode haver alguma coisa errada com o seu servidor de contatos.</string> + <string name="error_no_keys_to_trust">Não existe nenhuma chave utilizável para esse contato. Caso você tenha expurgado alguma chave dele, ele terá que gerar uma nova.</string> + <string name="error_trustkeys_title">Erro</string> + <string name="fetching_history_from_server">Obtendo histórico do servidor</string> + <string name="no_more_history_on_server">Nenhum outro histórico do servidor</string> + <string name="updating">Atualizando...</string> + <string name="password_changed">A senha foi alterada!</string> + <string name="could_not_change_password">Não foi possível alterar a senha</string> + <string name="otr_session_not_started">Envie uma mensagem para iniciar uma conversa criptografada</string> + <string name="ask_question">Faça uma pergunta</string> + <string name="smp_explain_question">Caso você e o seu contato tenham um segredo em comum que ninguém mais saiba (como, por exemplo, uma piada interna ou o que vocês comeram na última vez que se encontraram) vocês podem usar isso para verificar a impressão digital um do outro.\n\nVocê fornece uma dica ou pergunta para o seu contato responder. Atenção! A resposta diferencia maiúsculas de minúsculas.</string> + <string name="smp_explain_answer">O seu contato gostaria de verificar sua impressão digital através de um segredo compartilhado. O seu contato enviou a seguinte dica/questão para esse segredo.</string> + <string name="shared_secret_hint_should_not_be_empty">Sua dica não pode estar em branco</string> + <string name="shared_secret_can_not_be_empty">O seu segredo compartilhado não pode estar em branco</string> + <string name="manual_verification_explanation">Compare cuidadosamente a impressão digital abaixo com aquela do seu contato.\nVocê pode usar qualquer forma de comunicação segura, como um e-mail criptografado ou uma ligação telefônica para efetuar a troca.</string> + <string name="change_password">Alterar a senha</string> + <string name="current_password">Senha atual</string> + <string name="new_password">Nova senha</string> + <string name="password_should_not_be_empty">A senha não pode estar em branco</string> + <string name="enable_all_accounts">Habilitar todas as contas</string> + <string name="disable_all_accounts">Desabilitar todas as contas</string> + <string name="perform_action_with">Realizar a ação com</string> + <string name="no_affiliation">Sem afiliação</string> + <string name="no_role">Sem papel</string> + <string name="outcast">Banido</string> + <string name="member">Membro</string> + <string name="advanced_mode">Modo avançado</string> + <string name="grant_membership">Conceder filiação</string> + <string name="remove_membership">Revogar a filiação</string> + <string name="grant_admin_privileges">Conceder privilégios de administrador</string> + <string name="remove_admin_privileges">Revogar os privilégios de administrador</string> + <string name="remove_from_room">Remover da conferência</string> + <string name="could_not_change_affiliation">Não foi possível alterar a filiação de %s</string> + <string name="ban_from_conference">Banir da conferência</string> + <string name="removing_from_public_conference">Você está tentando remover %s de uma conferência pública. A única forma de fazer isso é banir essa pessoa para sempre.</string> + <string name="ban_now">Banir agora</string> + <string name="could_not_change_role">Não foi possível alterar o papel de %s</string> + <string name="public_conference">Conferência aberta ao público</string> + <string name="private_conference">Conferência privada, somente para membros</string> + <string name="conference_options">Opções da conferência</string> + <string name="members_only">Privada, somente para membros</string> + <string name="non_anonymous">Não-anônima</string> + <string name="moderated">Moderada</string> + <string name="you_are_not_participating">Você não está participando</string> + <string name="modified_conference_options">As opções da conferência foram alteradas.</string> + <string name="could_not_modify_conference_options">Não foi possível alterar as opções da conferência</string> + <string name="never">Nunca</string> + <string name="thirty_minutes">30 minutos</string> + <string name="one_hour">1 hora</string> + <string name="two_hours">2 horas</string> + <string name="eight_hours">8 horas</string> + <string name="until_further_notice">Até segunda ordem</string> + <string name="pref_input_options">Entrada</string> + <string name="pref_enter_is_send">Enter envia</string> + <string name="pref_enter_is_send_summary">Use o botão Enter para enviar a mensagem</string> + <string name="pref_display_enter_key">Exibir o botão Enter</string> + <string name="pref_display_enter_key_summary">Altere o botão de emoticons para um botão Enter</string> + <string name="audio">áudio</string> + <string name="video">vídeo</string> + <string name="image">imagem</string> + <string name="pdf_document">Documento PDF</string> + <string name="apk">Aplicativo Android</string> + <string name="vcard">Contato</string> + <string name="received_x_file">%s recebido</string> + <string name="disable_foreground_service">Desabilitar o serviço em segundo plano</string> + <string name="touch_to_open_conversations">Toque para abrir o Conversations</string> + <string name="avatar_has_been_published">O avatar foi publicado.</string> + <string name="sending_x_file">Enviando %s</string> + <string name="offering_x_file">Oferecendo %s</string> + <string name="hide_offline">Ocultar desconectados</string> + <string name="disable_account">Desabilitar a conta</string> + <string name="contact_is_typing">%s está digitando...</string> + <string name="contact_has_stopped_typing">%s parou de digitar</string> + <string name="pref_chat_states">Notificações de digitação</string> + <string name="pref_chat_states_summary">Permitir que seus contatos vejam quando você estiver digitando uma nova mensagem</string> + <string name="send_location">Enviar localização</string> + <string name="show_location">Exibir localização</string> + <string name="no_application_found_to_display_location">Não foi encontrado nenhum aplicativo para exibir a localização</string> + <string name="location">Localização</string> + <string name="received_location">Localização recebida</string> + <string name="title_undo_swipe_out_conversation">A conversa foi fechada</string> + <string name="title_undo_swipe_out_muc">Saiu da conferência</string> + <string name="pref_dont_trust_system_cas_title">Não confiar nas CAs do sistema</string> + <string name="pref_dont_trust_system_cas_summary">Todos os certificados devem ser aprovados manualmente</string> + <string name="pref_remove_trusted_certificates_title">Remover certificados</string> + <string name="pref_remove_trusted_certificates_summary">Excluir os certificados aprovados manualmente</string> + <string name="toast_no_trusted_certs">Nenhum certificado aprovado manualmente</string> + <string name="dialog_manage_certs_title">Remover certificados</string> + <string name="dialog_manage_certs_positivebutton">Excluir a seleção</string> + <string name="dialog_manage_certs_negativebutton">Cancelar</string> + <plurals name="toast_delete_certificates"> + <item quantity="one">%d certificado cancelado</item> + <item quantity="other">%d certificados cancelados</item> + </plurals> + <plurals name="select_contact"> + <item quantity="one">Selecionar %d contato</item> + <item quantity="other">Selecionar %d contatos</item> + </plurals> + <string name="pref_quick_action_summary">Trocar o botão enviar pelo de ação rápida</string> + <string name="pref_quick_action">Ação rápida</string> + <string name="none">Nenhuma</string> + <string name="recently_used">Usada mais recentemente</string> + <string name="choose_quick_action">Selecione a ação rápida</string> + <string name="search_for_contacts_or_groups">Pesquisar por contatos ou grupos</string> + <string name="send_private_message">Enviar mensagem privada</string> + <string name="user_has_left_conference">%s deixou a conferência.</string> + <string name="username">Nome de usuário</string> + <string name="username_hint">Nome de usuário</string> + <string name="invalid_username">Esse não é um nome de usuário válido</string> + <string name="download_failed_server_not_found">Não foi possível fazer o download: servidor não encontrado</string> + <string name="download_failed_file_not_found">Não foi possível fazer o download: arquivo não encontrado</string> + <string name="download_failed_could_not_connect">Não foi possível fazer o download: não foi possível conectar ao host</string> + <string name="pref_use_white_background">Usar fundo branco</string> + <string name="pref_use_white_background_summary">Exibir a mensagens recebidas como texto preto em um fundo branco</string> + <string name="account_status_tor_unavailable">Rede Tor não disponível</string> + <string name="server_info_broken">Quebrado</string> + <string name="pref_presence_settings">Presença</string> + <string name="pref_away_when_screen_off">Afastado quando a tela estiver desligada</string> + <string name="pref_away_when_screen_off_summary">Marcar o seu status como afastado quando a tela estiver desligada</string> + <string name="pref_xa_on_silent_mode">Não disponível quando em modo silencioso</string> + <string name="pref_xa_on_silent_mode_summary">Marcar o seu status como não disponível quando o dispositivo estiver em modo silencioso</string> + <string name="pref_show_connection_options">Configurações detalhadas da conexão</string> + <string name="pref_show_connection_options_summary">Exibe o nome de host e configurações da porta ao configurar uma conta</string> + <string name="hostname_example">xmpp.example.com</string> + <string name="action_add_account_with_certificate">Adicionar uma conta com certificado</string> + <string name="unable_to_parse_certificate">Não foi possível analisar o certificado</string> + <string name="authenticate_with_certificate">Deixe em branco para autenticar com um certificado</string> + <string name="mam_prefs">Preferências de arquivamento</string> + <string name="server_side_mam_prefs">Preferências de arquivamento no servidor</string> + <string name="fetching_mam_prefs">Obtendo as preferências de arquivamento. Por favor aguarde...</string> + <string name="unable_to_fetch_mam_prefs">Não foi possível obter as preferências de arquivamento</string> + <string name="captcha_ocr">Texto captcha</string> + <string name="captcha_required">Captcha obrigatório</string> + <string name="captcha_hint">digite o texto da imagem</string> + <string name="certificate_chain_is_not_trusted">A cadeia de certificação não é confiável</string> + <string name="jid_does_not_match_certificate">O ID Jaber não corresponde ao certificado</string> + <string name="action_renew_certificate">Renovar o certificado</string> + <string name="error_fetching_omemo_key">Ocorreu um erro na obtenção da chave OMEMO!</string> + <string name="verified_omemo_key_with_certificate">A chave OMEMO foi verificada com o certificado.</string> + <string name="device_does_not_support_certificates">O seu dispositivo não suporta a seleção de certificados de clientes.</string> + <string name="pref_connection_options">Conexão</string> + <string name="account_settings_hostname">Nome do host</string> + <string name="account_settings_port">Porta</string> + <string name="not_a_valid_port">Esse número de porta não é válido</string> + <string name="not_valid_hostname">Esse nome de host não é válido</string> + <string name="connected_accounts">%1$d de %2$d contas conectadas</string> + <plurals name="x_messages"> + <item quantity="one">%d mensagem</item> + <item quantity="other">%d mensagens</item> + </plurals> + <string name="shared_file_with_x">Arquivo compartilhado com %s</string> + <string name="shared_image_with_x">Imagem compartilhada com %s</string> + <string name="no_storage_permission">O Conversations necessita de acesso ao armazenamento externo</string> + <string name="sync_with_contacts">Sincronizar com os contatos</string> + <string name="sync_with_contacts_long">O Conversations quer procurar por correspondências entre contatos da sua conta Jabber e do seu telefone, para complementar as informações de nome completo e avatares\n\nO Conversations fará a verificação localmente, sem enviar nenhuma informação sua para o servidor\n\nVocê será solicitado a fornecer permissão de acesso aos seus contatos agora.</string> + <string name="certificate_information">Informação do certificado</string> + <string name="certificate_subject">Assunto</string> + <string name="certificate_issuer">Emitente</string> + <string name="certificate_cn">Common Name</string> + <string name="certificate_o">Organização</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(Não disponível)</string> + <string name="certificate_not_found">Não foi encontrado nenhum certificado</string> + <string name="notify_on_all_messages">Notificar em todas as mensagens</string> + <string name="notify_only_when_highlighted">Notificar somente quando destacado</string> + <string name="notify_never">Notificações desabilitadas</string> + <string name="notify_paused">Notificações pausadas</string> + <string name="pref_picture_compression">Comprimir imagens</string> + <string name="pref_picture_compression_summary">Redimensiona e comprime as imagens</string> + <string name="always">Sempre</string> + <string name="automatically">Automaticamente</string> + <string name="battery_optimizations_enabled">Otimizações de bateria habilitadas</string> + <string name="battery_optimizations_enabled_explained">O seu dispositivo está aplicando uma otimização de bateria intensa no Conversations, que pode levar a atraso nas notificações ou até mesmo perda de mensagens.\nÉ recomendado desabilitar isso.</string> + <string name="battery_optimizations_enabled_dialog">O seu dispositivo está aplicando uma otimização de bateria intensa no Conversations, que pode levar a atraso nas notificações ou até mesmo perda de mensagens.\nAgora você será solicitado a desabilitá-la.</string> + <string name="disable">Desabilitar</string> + <string name="selection_too_large">A área selecionada é muito grande</string> + <string name="no_accounts">(Nenhuma conta ativa)</string> + <string name="this_field_is_required">Este campo é necessário</string> +</resources> diff --git a/src/main/res/values-pt/strings.xml b/src/main/res/values-pt/strings.xml index 559ae6cf..30265948 100644 --- a/src/main/res/values-pt/strings.xml +++ b/src/main/res/values-pt/strings.xml @@ -9,7 +9,6 @@ <string name="action_secure">Conversa segura</string> <string name="action_add_account">Adicionar conta</string> <string name="action_edit_contact">Editar nome</string> - <string name="action_add_phone_book">Adicionar aos contatos</string> <string name="action_block_contact">Bloquear contato</string> <string name="action_unblock_contact">Desbloquear contato</string> <string name="action_block_domain">Bloquear domínio</string> @@ -27,7 +26,6 @@ <string name="minutes_ago">%d minutos atrás</string> <string name="unread_conversations">Conversas não lidas</string> <string name="sending">enviando...</string> - <string name="encrypted_message">Descriptografando mensagem. Por favor aguarde...</string> <string name="nick_in_use">O apelido já está em uso</string> <string name="admin">Administrador</string> <string name="owner">Dono</string> @@ -72,9 +70,7 @@ <string name="clear_conversation_history">Limpar o histórico de conversas</string> <string name="clear_histor_msg">Você deseja remover todas as mensagens nessa conversa?\n\n<b>Atenção:<b> Isso não irá influenciar mensagens salvas em outros dispositivos ou servidores.</string> <string name="delete_messages">Remover mensagens</string> - <string name="also_end_conversation">Finalizar essa conversa ao final</string> <string name="choose_presence">Escolha a presença do contato</string> - <string name="send_plain_text_message">Enviar mensagem de texto puro</string> <string name="send_otr_message">Enviar mensagem criptografada com OTR</string> <string name="send_pgp_message">Enviar mensagem criptografada com OpenPGP</string> <string name="your_nick_has_been_changed">Seu apelido foi alterado</string> @@ -92,23 +88,18 @@ <string name="pref_xmpp_resource_summary">O nome pelo qual esse cliente se identifica</string> <string name="pref_accept_files">Aceitar arquivos</string> <string name="pref_accept_files_summary">Automaticamente aceita arquivos menores que...</string> - <string name="pref_notification_settings">Configurações de notificação</string> <string name="pref_notifications">Notificações</string> <string name="pref_notifications_summary">Notificar quando uma nova mensagem for recebida</string> <string name="pref_vibrate">Vibrar</string> <string name="pref_vibrate_summary">Vibrar também quando uma nova mensagem for recebida</string> <string name="pref_sound">Som</string> <string name="pref_sound_summary">Tocar um som com a notificação</string> - <string name="pref_conference_notifications">Notificações de conferência</string> - <string name="pref_conference_notifications_summary">Sempre notificar quando uma nova mensagem de conferencia for recebida ao invés de apenas quando a mesma for ressaltada</string> <string name="pref_notification_grace_period">Período de carência da notificação</string> <string name="pref_notification_grace_period_summary">Desativar notificações por um curto período após a copia oculta ser recebida</string> - <string name="pref_advanced_options">Opções avançadas</string> <string name="pref_never_send_crash">Nunca enviar relatórios de quebra</string> <string name="pref_never_send_crash_summary">Ao enviar os stack traces você ajuda o desenvolvimento do aplicativo</string> <string name="pref_confirm_messages">Confirmar mensanges</string> <string name="pref_confirm_messages_summary">Permitir que um contato saiba quando você recebeu e leu uma mensagem</string> - <string name="pref_ui_options">Opções de UI</string> <string name="openpgp_error">O OpenKeychain informou um erro</string> <string name="error_decrypting_file">Erro de I/O de critpografia</string> <string name="accept">Aceitar</string> @@ -139,7 +130,6 @@ <string name="account_status_regis_not_sup">O servidor não aceita o registro</string> <string name="account_status_security_error">Erro de segurança</string> <string name="account_status_incompatible_server">Servidor incompatível</string> - <string name="encryption_choice_none">Texto puro</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> <string name="mgmt_account_edit">Editar conta</string> @@ -159,7 +149,6 @@ <string name="passwords_do_not_match">As senhas não combina</string> <string name="invalid_jid">Esse não é um ID Jabber válido</string> <string name="error_out_of_memory">Memória insuficiente. A imagem é muito grande</string> - <string name="add_phone_book_text">Você tem certeza que deseja adicionar %s à sua lista de contato do telefone?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">disponível para conversa</string> <string name="contact_status_away">fora</string> @@ -231,7 +220,6 @@ <string name="skip">Pular</string> <string name="disable_notifications">Desativar notificações</string> <string name="disable_notifications_for_this_conversation">Desativar notificações para essa conversa</string> - <string name="notifications_disabled">As notificações foram desativadas</string> <string name="enable">Ativar</string> <string name="conference_requires_password">Essa conferencia requer uma senha</string> <string name="enter_password">Informar a senha</string> @@ -239,12 +227,10 @@ <string name="request_now">Solicitar agora</string> <string name="delete_fingerprint">Remover impressão</string> <string name="sure_delete_fingerprint">Tem certeza que deseja remover essa assinatura?</string> - <string name="pref_encryption_settings">Configurações de criptografia</string> <string name="pref_force_encryption">Forçar criptografia ponto-a-ponto</string> <string name="pref_force_encryption_summary">Sempre envie mensagem criptografada (exceto para conferências)</string> <string name="pref_dont_save_encrypted">Não salve mensagens criptografadas</string> <string name="pref_dont_save_encrypted_summary">Atenção: Isso pode levar a perda de mensagens</string> - <string name="pref_expert_options">Opções de expert</string> <string name="pref_expert_options_summary">Por favor tenha cuidado com essas</string> <string name="title_activity_about">Sobre Conversas</string> <string name="pref_about_conversations_summary">Informação de licença e construção</string> @@ -274,7 +260,6 @@ <string name="two_hours">2 horas</string> <string name="eight_hours">8 horas</string> <string name="until_further_notice">Até segunda ordem</string> - <string name="pref_input_options">Opções de entrada</string> <string name="pref_enter_is_send">O enter envia</string> <string name="pref_enter_is_send_summary">Use o enter para enviar a mensagem</string> <string name="pref_display_enter_key">Exibir tecla enter</string> @@ -286,7 +271,6 @@ <string name="vcard">Contato</string> <string name="sending_x_file">Enviando %s</string> <string name="offering_x_file">Oferecendo %s</string> - <string name="contact_is_typing">%s está digitando...</string> <string name="contact_has_stopped_typing">%s parou de digitar</string> <string name="pref_chat_states">Notificações de digitação</string> <string name="send_location">Enviar localização</string> diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index 9cbfcdbf..e55b21f2 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -1,15 +1,15 @@ <?xml version='1.0' encoding='UTF-8'?> <resources> - <string name="action_settings">Configuratie</string> + <string name="action_settings">Setari</string> <string name="action_add">Conversatie noua</string> <string name="action_accounts">Configureaza conturi</string> - <string name="action_end_conversation">Termina conversatie</string> + <string name="action_end_conversation">Termina conversatia</string> <string name="action_contact_details">Detalii contact</string> <string name="action_muc_details">Detalii conferinta</string> <string name="action_secure">Securizeaza conferinta</string> <string name="action_add_account">Adauga cont</string> <string name="action_edit_contact">Editeaza nume</string> - <string name="action_add_phone_book">Adauga in agenda</string> + <string name="action_add_phone_book">Adauga la lista de contacte</string> <string name="action_delete_contact">Sterge din lista</string> <string name="action_block_contact">Blocheaza contact</string> <string name="action_unblock_contact">Deblocheaza contact</string> @@ -19,18 +19,19 @@ <string name="title_activity_settings">Configuratie</string> <string name="title_activity_conference_details">Detalii conferinta</string> <string name="title_activity_contact_details">Detalii contact</string> - <string name="title_activity_sharewith">Distribuie catre Conversatie</string> - <string name="title_activity_start_conversation">Porneste Conversatie</string> + <string name="title_activity_sharewith">Distribuie catre Conversations</string> + <string name="title_activity_start_conversation">Porneste conversatie</string> <string name="title_activity_choose_contact">Alege contact</string> <string name="title_activity_block_list">Blocheaza lista</string> <string name="just_now">in acest moment</string> - <string name="minute_ago">acuma 1 minut</string> - <string name="minutes_ago">acuma %d minute</string> - <string name="unread_conversations">Conversatii necitite</string> + <string name="minute_ago">acum un minut</string> + <string name="minutes_ago">acum %d minute</string> + <string name="unread_conversations">conversatii necitite</string> <string name="sending">trimitere...</string> - <string name="encrypted_message">Decriptez mesaj. Te rog asteapta...</string> + <string name="message_decrypting">Decriptez mesaj. Te rog asteapta...</string> + <string name="pgp_message">Mesaj criptat cu OpenPGP</string> <string name="nick_in_use">Nume utilizator este deja folosit.</string> - <string name="admin">Admin</string> + <string name="admin">Administrator</string> <string name="owner">Proprietar</string> <string name="moderator">Moderator</string> <string name="participant">Participant</string> @@ -41,11 +42,12 @@ <string name="block_domain_text">Blocheaza toate contactele de la %s?</string> <string name="unblock_domain_text">Deblocheaza toate contactele de la %s?</string> <string name="contact_blocked">Contact blocat</string> - <string name="remove_bookmark_text">Ai dori sa stergi pe %s ca semn de carte? Conversatia asociata cu acest semn de carte nu va fi stearsa.</string> + <string name="remove_bookmark_text">Ai dori sa stergi pe %s din semne de carte? Conversatia asociata cu acest semn de carte nu va fi stearsa.</string> <string name="register_account">Inregistreaza un cont nou pe server</string> <string name="change_password_on_server">Schimba parola pe server</string> - <string name="start_conversation">Porneste Conversatie</string> - <string name="invite_contact">Invita Contact</string> + <string name="share_with">Partajeaza cu...</string> + <string name="start_conversation">Porneste conversatie</string> + <string name="invite_contact">Invita contact</string> <string name="contacts">Contacte</string> <string name="cancel">Anuleaza</string> <string name="set">Seteaza</string> @@ -56,37 +58,505 @@ <string name="unblock">Deblocheaza</string> <string name="save">Salveaza</string> <string name="ok">DA</string> - <string name="crash_report_title">Conversatii s-a oprit neasteptat</string> - <string name="crash_report_message">Trimitand date ajuti la dezvoltarea aplicatiei Conversatii\n<b>Atentie:</b> Se va utiliza contul XMPP pentru a trimite informatii catre programatori.</string> - <string name="send_now">Trimie acum</string> - <string name="send_never">Nu mai intreba in viitor</string> + <string name="crash_report_title">Conversations s-a oprit neasteptat</string> + <string name="crash_report_message">Trimitand jurnalele de eroare ajuti la buna dezvoltarea a aplicatiei Conversations\n<b>Atentie:</b> Se va utiliza contul XMPP pentru a trimite informatiile din jurnal catre programatori.</string> + <string name="send_now">Trimite acum</string> + <string name="send_never">Nu ma mai intreba</string> <string name="problem_connecting_to_account">Nu ma pot conecta la cont</string> <string name="problem_connecting_to_accounts">Nu ma pot conecta la conturi multiple</string> <string name="touch_to_fix">Apasa aici pentru a configura conturile tale</string> <string name="attach_file">Ataseaza fisier</string> <string name="not_in_roster">Contactul nu este in lista ta. Ai vrea sa il adaugi?</string> <string name="add_contact">Adauga contact</string> - <string name="send_failed">Trimitere esuata</string> - <string name="send_rejected">rejectat</string> + <string name="send_failed">trimitere esuata</string> + <string name="send_rejected">respins</string> <string name="preparing_image">Pregatesc imaginea pentru transmisie</string> - <string name="action_clear_history">Sterge istoria</string> - <string name="clear_conversation_history">Sterge istoria conversatiei</string> - <string name="clear_histor_msg">Doresti sa stergi toate mesajele din Conversatii?\n\n<b>Atentie:</b> Aceasta actiune nu va influenta mesajele aflate pe alte telefoane/tabelete/servere.</string> + <string name="action_clear_history">Sterge istoric</string> + <string name="clear_conversation_history">Sterge istoricul conversatiei</string> + <string name="clear_histor_msg">Doresti sa stergi toate mesajele din aceasta conversatie?\n\n<b>Atentie:</b> Aceasta actiune nu va influenta mesajele aflate pe alte telefoane/tabelete/servere.</string> <string name="delete_messages">Sterge mesajele</string> - <string name="also_end_conversation">Termina conversatia aceasta dupa</string> + <string name="also_end_conversation">Dupa, incheie conversatia</string> <string name="choose_presence">Alege prezenta pentru a contacta</string> - <string name="send_plain_text_message">Trimite text necriptat</string> + <string name="send_unencrypted_message">Trimite mesaje necriptate</string> <string name="send_otr_message">Trimite mesaj criptat cu OTR</string> + <string name="send_omemo_message">Trimite mesaj criptat cu OMEMO</string> + <string name="send_omemo_x509_message">Trimite mesaj criptat cu v\\OMEMO</string> <string name="send_pgp_message">Trimite mesaj criptat cu OpenPGP</string> <string name="your_nick_has_been_changed">Numele tau a fost schimbat</string> <string name="send_unencrypted">Trimite necriptat</string> <string name="decryption_failed">Decriptia a esuat. Poate nu ai cheia privata corecta.</string> <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Coonversatii utilizeaza o aplicatia externa <b>OpenKeychain</b> pentru a cripta si decripta mesaje si a administra cheile publice.\n\nOpenKeychain este licentiat sub GPLv3 si se gaseste pentru copiere pe F-Droid si Google Play.\n\n<small>(Te rog sa repornesti Conversatii dupa.)</small></string> + <string name="openkeychain_required_long">Conversations utilizeaza o aplicatia externa <b>OpenKeychain</b> pentru a cripta si decripta mesaje si a administra cheile publice.\n\nOpenKeychain este licentiat sub GPLv3 si se gaseste pentru copiere pe F-Droid si Google Play.\n\n<small>(Te rog sa repornesti Conversations dupa)</small></string> <string name="restart">Reporneste</string> <string name="install">Instaleaza</string> - <string name="offering">Transmit...</string> - <string name="waiting">In asteptare...</string> + <string name="openkeychain_not_installed">Te rog instaleaza OpenKeychain</string> + <string name="offering">transmit...</string> + <string name="waiting">in asteptare...</string> <string name="no_pgp_key">Nu am gasit cheie OpenPGP</string> - <string name="contact_has_no_pgp_key">Conversatii nu a putut sa cirpteze mesajele tale din cauza contactului care nu isi anunta cheia publica.\n\n<small>Roaga contactul sa isi configureze OpenPGP.</small></string> + <string name="contact_has_no_pgp_key">Conversations nu a putut sa cripteze mesajele tale din cauza contactului care nu isi anunta cheia publica.\n\n<small>Roaga contactul sa isi configureze OpenPGP.</small></string> + <string name="no_pgp_keys">Nu am gasit chei OpenPGP</string> + <string name="contacts_have_no_pgp_keys">Conversations nu poate cripta mesajele tale pentru contactele tale care nu isi anunta cheia publica.\n\n<small>Te rog cere contactelor sa configureze OpenPGP.</small></string> + <string name="encrypted_message_received"><i>Ai primit mesaj criptat. Apasa aici pentru a-l decripta.</i></string> + <string name="pref_general">General</string> + <string name="pref_xmpp_resource">Nume client XMPP</string> + <string name="pref_xmpp_resource_summary">Numele cu care acest client se identifica</string> + <string name="pref_accept_files">Accepta fisiere</string> + <string name="pref_accept_files_summary">Accepta automat fisiere mai mici decat...</string> + <string name="pref_notification_settings">Notificare</string> + <string name="pref_notifications">Notificari</string> + <string name="pref_notifications_summary">Notifica cand un nou mesaj este primit</string> + <string name="pref_vibrate">Vibreaza</string> + <string name="pref_vibrate_summary">Vibreaza cand un nou mesaj este primit</string> + <string name="pref_sound">Sunet</string> + <string name="pref_sound_summary">Ton de apel pentru notificare</string> + <string name="pref_notification_grace_period">Perioada de gratie notificari </string> + <string name="pref_notification_grace_period_summary">Opreste notificari pentru o scurta perioada dupa ce o copie a mesajului a fost primita</string> + <string name="pref_advanced_options">Optiuni avansate</string> + <string name="pref_never_send_crash">Nu trimite rapoarte de erori</string> + <string name="pref_never_send_crash_summary">Trimitand date ajuti la dezvoltarea aplicatiei Conversations\n<b>Atentie:</b> Se va utiliza contul XMPP pentru a trimite informatii catre programatori.</string> + <string name="pref_confirm_messages">Confirma mesaje</string> + <string name="pref_confirm_messages_summary">Notifica contactul cand ai primit un mesaj si l-ai citit</string> + <string name="pref_ui_options">Optiuni interfata</string> + <string name="openpgp_error">OpenKeychain a raportat o eroare</string> + <string name="error_decrypting_file">Eroare I/O la decriptarea fisierului</string> + <string name="accept">Accepta</string> + <string name="error">A aparut o eroare</string> + <string name="pref_grant_presence_updates">Trimite actualizari de prezenta</string> + <string name="pref_grant_presence_updates_summary">Acorda si cere anticipat abonarea la actualizarile de prezenta pentru contactele create de tine</string> + <string name="subscriptions">Abonari</string> + <string name="your_account">Contul tau</string> + <string name="keys">Chei</string> + <string name="send_presence_updates">Trimite actualizari de prezenta</string> + <string name="receive_presence_updates">Primeste actualizari de prezenta</string> + <string name="ask_for_presence_updates">Cere actualizari de prezenta</string> + <string name="attach_choose_picture">Alege imagine</string> + <string name="attach_take_picture">Fa o poza</string> + <string name="preemptively_grant">Acorda anticipat cererea de abonare</string> + <string name="error_not_an_image_file">Fisierul selectat nu este o imagine</string> + <string name="error_compressing_image">Eroare la conversia fisierului de imagine</string> + <string name="error_file_not_found">Fisierul nu a fost gasit</string> + <string name="error_io_exception">Eroare I/O gemerala. Poate ai ramas fara spatiu liber?</string> + <string name="error_security_exception_during_image_copy">Aplicatia folosita pentru selectia acestei imagini nu a oferit destule permisiuni pentru a putea citi fisierul.\n\n<small>Foloseste un alt manager de fisiere pentru a alege o imagine</small></string> + <string name="account_status_unknown">Necunoscut</string> + <string name="account_status_disabled">Dezactivat temporar</string> + <string name="account_status_online">Conectat</string> + <string name="account_status_connecting">In curs de conectare\u2026</string> + <string name="account_status_offline">Deconectat</string> + <string name="account_status_unauthorized">Neautorizat</string> + <string name="account_status_not_found">Serverul nu a fost gasit</string> + <string name="account_status_no_internet">Fara conexiune</string> + <string name="account_status_regis_fail">Inregistrare esuata</string> + <string name="account_status_regis_conflict">Nume de utilizator deja alocat</string> + <string name="account_status_regis_success">Inregistrare completa</string> + <string name="account_status_regis_not_sup">Acest server nu permite inregistrarea</string> + <string name="account_status_security_error">Eroare de securitate</string> + <string name="account_status_incompatible_server">Server incompatibil</string> + <string name="encryption_choice_unencrypted">Ne criptat</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">OpenGPG</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Editare cont</string> + <string name="mgmt_account_delete">Sterge cont</string> + <string name="mgmt_account_disable">Dezactivare temporara</string> + <string name="mgmt_account_publish_avatar">Publica avatar</string> + <string name="mgmt_account_publish_pgp">Publica cheia publica OpenPGP</string> + <string name="mgmt_account_enable">Activeaza cont</string> + <string name="mgmt_account_are_you_sure">Esti sigur?</string> + <string name="mgmt_account_delete_confirm_text">Daca iti stergi contul intregul istoric de conversatii va fi pierdut</string> + <string name="attach_record_voice">Inregistrare voce</string> + <string name="account_settings_jabber_id">ID-ul Jabber</string> + <string name="account_settings_password">Parola</string> + <string name="account_settings_example_jabber_id">username@example.com</string> + <string name="account_settings_confirm_password">Confirma parola</string> + <string name="password">Parola</string> + <string name="confirm_password">Confirma parola</string> + <string name="passwords_do_not_match">Parolele nu sunt identice</string> + <string name="invalid_jid">Acesta nu este un ID Jabber valabil</string> + <string name="error_out_of_memory">Memorie epuizata. Imaginea este prea mare.</string> + <string name="add_phone_book_text">Vrei sa adaugi pe %s in lista de contacte?</string> + <string name="contact_status_online">conectat</string> + <string name="contact_status_free_to_chat">disponibil pentru conversatie</string> + <string name="contact_status_away">plecat</string> + <string name="contact_status_extended_away">plecat departe</string> + <string name="contact_status_do_not_disturb">nu deranja</string> + <string name="contact_status_offline">deconectat</string> + <string name="muc_details_conference">Conferinta</string> + <string name="muc_details_other_members">Alti membri</string> + <string name="server_info_show_more">Informatii server</string> + <string name="server_info_mam">XEP-0313: MAM</string> + <string name="server_info_carbon_messages">XEP-0280: Copii indigo mesaje</string> + <string name="server_info_csi">XEP-0352: Indicator stare client</string> + <string name="server_info_blocking">XEP-0191: Comanda blocare</string> + <string name="server_info_roster_version">XEP-0237: Creare de versiuni lista</string> + <string name="server_info_stream_management">XEP-0198: Management flux</string> + <string name="server_info_pep">XEP-0163: PEP (Avatare / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: Incarcare fisiere prin HTTP</string> + <string name="server_info_push">XEP-0357: Push</string> + <string name="server_info_available">disponibil</string> + <string name="server_info_unavailable">indisponibil</string> + <string name="missing_public_keys">Cheile publice ce nu au fost anuntate</string> + <string name="last_seen_now">vazut ultima data adineauri</string> + <string name="last_seen_min">vazut ultima data acum un minut</string> + <string name="last_seen_mins">vazut ultima data acum %d minute</string> + <string name="last_seen_hour">vazut ultima data acum o ora</string> + <string name="last_seen_hours">vazut ultima data acum %d ore</string> + <string name="last_seen_day">vazut ultima data acum o zi</string> + <string name="last_seen_days">vazut ultima data acum %d zile</string> + <string name="never_seen">niciodata vazut</string> + <string name="install_openkeychain">Mesaj criptat. Te rog instaleaza OpenKeychain pentru a-l putea decripta.</string> + <string name="unknown_otr_fingerprint">Amprenta OTR necunoscuta</string> + <string name="openpgp_messages_found">A fost gasit un mesaj criptat cu OpenPGP</string> + <string name="reception_failed">Receptie esuata</string> + <string name="your_fingerprint">Amprenta ta</string> + <string name="otr_fingerprint">Amprenta OTR</string> + <string name="omemo_fingerprint">Amprenta OMEMO</string> + <string name="omemo_fingerprint_x509">Amprenta v\\OMEMO</string> + <string name="omemo_fingerprint_selected_message">Amprenta OMEMO a mesajului</string> + <string name="omemo_fingerprint_x509_selected_message">Amprenta v\\OMEMO a mesajului</string> + <string name="this_device_omemo_fingerprint">Amprenta OMEMO proprie</string> + <string name="other_devices">Alte dispozitive</string> + <string name="trust_omemo_fingerprints">Amprente OMEMO de incredere</string> + <string name="fetching_keys">Se preiau cheile...</string> + <string name="done">Gata</string> + <string name="verify">Verifica</string> + <string name="decrypt">Decripteaza</string> + <string name="conferences">Conferinte</string> + <string name="search">Cauta</string> + <string name="create_contact">Adauga contact</string> + <string name="enter_contact">Introdu contact</string> + <string name="join_conference">Alatura-te conferintei</string> + <string name="delete_contact">Sterge contact</string> + <string name="view_contact_details">Arata detalii contact</string> + <string name="block_contact">Blocheaza contact</string> + <string name="unblock_contact">Deblocheaza contact</string> + <string name="create">Creeaza</string> + <string name="select">Selecteaza</string> + <string name="contact_already_exists">Contactul exista deja</string> + <string name="join">Alatura-te</string> + <string name="conference_address">Adresa conferinta</string> + <string name="conference_address_example">room@conference.example.com</string> + <string name="save_as_bookmark">Salveaza semn de carte</string> + <string name="delete_bookmark">Sterge semn de carte</string> + <string name="bookmark_already_exists">Acest semn de carte exista</string> + <string name="you">Tu</string> + <string name="action_edit_subject">Editeaza titlul conferintei</string> + <string name="conference_not_found">Conferinta nu a fost gasita</string> + <string name="conference_unknown_error">A fost primita o eroare necunoscuta</string> + <string name="leave">Paraseste</string> + <string name="contact_added_you">Contactul a fost adaugat in lista </string> + <string name="add_back">Adauga inapoi</string> + <string name="contact_has_read_up_to_this_point">%s a citit pana in acest loc</string> + <string name="publish">Publica</string> + <string name="touch_to_choose_picture">Atinge avatarul pentru a selecta o poza din galerie</string> + <string name="publish_avatar_explanation">Tine minte: Toti cei abonati la actualizarile tale de prezenta vor putea sa vada aceasta poza</string> + <string name="publishing">Se publica...</string> + <string name="error_publish_avatar_server_reject">Acest server v-a refuzat publicarea</string> + <string name="error_publish_avatar_converting">Ceva nu a mers bine in timpul conversiei imaginii</string> + <string name="error_saving_avatar">Nu s-a putut salva avatarul pe disc</string> + <string name="or_long_press_for_default">(Sau apasa indelung pentru a reseta la implicit)</string> + <string name="error_publish_avatar_no_server_support">Acest server nu permite publicarea de avatare</string> + <string name="private_message">sopteste</string> + <string name="private_message_to">catre %s</string> + <string name="send_private_message_to">Trimite mesaj privat catre %s</string> + <string name="connect">Conectare</string> + <string name="account_already_exists">Acest cont exista deja</string> + <string name="next">Urmatoarea</string> + <string name="server_info_session_established">Sesiune curenta stabilita</string> + <string name="additional_information">Informatii aditionale</string> + <string name="skip">Sari</string> + <string name="disable_notifications">Dezactiveaza notificari</string> + <string name="disable_notifications_for_this_conversation">Dezactiveaza notificarile pentru aceasta conversatie</string> + <string name="enable">Activeaza</string> + <string name="conference_requires_password">Conferinta necesita parola</string> + <string name="enter_password">Introdu parola</string> + <string name="missing_presence_updates">Au lipsit actualizarile de prezenta de la contact</string> + <string name="request_presence_updates">Te rog mai intai cere actualizari de prezente de la contact.\n\n<small>Asta va fi folosita pentru a determina ce aplicatii client foloseste contactul tau</small></string> + <string name="request_now">Cere acum</string> + <string name="delete_fingerprint">Sterge amprenta</string> + <string name="sure_delete_fingerprint">Sigur vrei sa stergi amprenta</string> + <string name="ignore">Ignora</string> + <string name="without_mutual_presence_updates"><b>Atentie:</b> Trimitand aceasta fara actualizari de prezenta reciproce, ar putea produce probleme neprevazute.\n\n<small>Mergi la lista de contacte, la detalii, sa iti verifici abonarile la actualizarile de prezenta.</small></string> + <string name="pref_security_settings">Siguranta</string> + <string name="pref_force_encryption">Forteaza criptarea conexiunii de la un capat la altul</string> + <string name="pref_force_encryption_summary">Trimite mereu mesajele criptate (exceptand conferintele)</string> + <string name="pref_allow_message_correction">Permite corectia mesajelor</string> + <string name="pref_allow_message_correction_summary">Permite contactelor sa isi editeze mesajele din trecut</string> + <string name="pref_dont_save_encrypted">Nu salva mesaje criptate</string> + <string name="pref_dont_save_encrypted_summary">Atentie: Asta poate duce la pierderea de mesaje</string> + <string name="pref_expert_options">Optiuni expert</string> + <string name="pref_expert_options_summary">Te rog sa fi atent cu astea</string> + <string name="title_activity_about">Despre Conversations</string> + <string name="pref_about_conversations_summary">Informatii despre versiune si licenta</string> + <string name="title_pref_quiet_hours">Ore de liniste</string> + <string name="title_pref_quiet_hours_start_time">Ora de pornire</string> + <string name="title_pref_quiet_hours_end_time">Ora de oprire</string> + <string name="title_pref_enable_quiet_hours">Activeaza orar de liniste</string> + <string name="pref_quiet_hours_summary">Notificarile vor fi reduse la tacere in timpul orelor de liniste</string> + <string name="pref_use_larger_font">Mareste marimea literelor</string> + <string name="pref_use_larger_font_summary">Foloseste marimea mai mare de text in toata aplicatia</string> + <string name="pref_use_send_button_to_indicate_status">Butonul de trimitere indica starea</string> + <string name="pref_use_indicate_received">Cere raport de primire</string> + <string name="pref_use_indicate_received_summary">Mesajele primite vor fi marcate cu un semn verde daca este suportat</string> + <string name="pref_use_send_button_to_indicate_status_summary">Coloreaza butonul de trimitere pentru a indica starea contactului</string> + <string name="pref_expert_options_other">Altele</string> + <string name="pref_conference_name">Titlu conferinta</string> + <string name="pref_conference_name_summary">Foloseste subiectul camerei in locul JID pentru a identifica conferinta</string> + <string name="pref_autojoin">Alatura-te automat conferintelor</string> + <string name="pref_autojoin_summary">Respecta setarea de alaturare automata la o conferinta conform semnului de carte</string> + <string name="toast_message_otr_fingerprint">Amprenta OTR copiata in memorie</string> + <string name="toast_message_omemo_fingerprint">Amprenta OMEMO copiata in memorie!</string> + <string name="conference_banned">Ti-a fost interzis accesul la aceasta conferinta</string> + <string name="conference_members_only">Aceasta conferinta este rezervata membrilor</string> + <string name="conference_kicked">Ai fost dat afara din conferinta</string> + <string name="using_account">folosind cont %s</string> + <string name="checking_x">Verifica %s pe gazda HTTP</string> + <string name="not_connected_try_again">Nu esti conectat. Incearca din nou mai tarziu.</string> + <string name="check_x_filesize">Verifica marimea %s</string> + <string name="message_options">Optiuni mesaje</string> + <string name="copy_text">Copiaza text</string> + <string name="copy_original_url">Copiaza URL original</string> + <string name="send_again">Trimite din nou</string> + <string name="file_url">URL fisier</string> + <string name="message_text">Mesaj text</string> + <string name="url_copied_to_clipboard">URL copiat in memorie</string> + <string name="message_copied_to_clipboard">Mesaj copiat in memorie</string> + <string name="image_transmission_failed">Transmisia imaginii a esuat</string> + <string name="scan_qr_code">Scaneaza cod QR</string> + <string name="show_qr_code">Arata codul QR</string> + <string name="show_block_list">Arata lista blocata</string> + <string name="account_details">Detalii cont</string> + <string name="verify_otr">Verifica OTR</string> + <string name="remote_fingerprint">Amprenta la distanta</string> + <string name="scan">scaneaza</string> + <string name="smp">Socialist Millionaire Protocol</string> + <string name="shared_secret_hint">Indiciu sau Intrebare</string> + <string name="shared_secret_secret">Secret impartasit</string> + <string name="confirm">Confirma</string> + <string name="in_progress">In desfasurare</string> + <string name="respond">Raspunde</string> + <string name="failed">Esuat</string> + <string name="secrets_do_not_match">Secretele nu se potrivesc</string> + <string name="try_again">Incearca din nou</string> + <string name="finish">Incheie</string> + <string name="verified">Verificat!</string> + <string name="smp_requested">Contactul a cerut verificare SMP</string> + <string name="no_otr_session_found">Nu s-a gasit nici o sesiune OTR valida</string> + <string name="conversations_foreground_service">Conversations</string> + <string name="pref_keep_foreground_service">Pastreaza serviciul activ in prim plan</string> + <string name="pref_keep_foreground_service_summary">Previne inchiderea conexiunii de catre sistemul de operare.</string> + <string name="pref_export_logs">Exporta jurnale</string> + <string name="pref_export_logs_summary">Scrie jurnal pe card SD</string> + <string name="notification_export_logs_title">Se scrie jurnal pe card SD</string> + <string name="choose_file">Alege un fisier</string> + <string name="receiving_x_file">Primesc %1$s (%2$d%% complet)</string> + <string name="download_x_file">Descarca %s</string> + <string name="file">fisier</string> + <string name="open_x_file">Deschide %s</string> + <string name="sending_file">trimit (%1$d%% complet)</string> + <string name="preparing_file">Pregatire fisier pentru transmisie</string> + <string name="x_file_offered_for_download">%s oferit spre descarcare</string> + <string name="cancel_transmission">Anuleaza transmisiunea</string> + <string name="file_transmission_failed">transmisie fisier esuata</string> + <string name="file_deleted">Fisierul a fost sters</string> + <string name="no_application_found_to_open_file">Nu s-a gasi nici o aplicatie care sa deschida fisierul</string> + <string name="could_not_verify_fingerprint">Nu s-a putut verifica amprenta</string> + <string name="manually_verify">Verifica manual</string> + <string name="are_you_sure_verify_fingerprint">Sigur vrei sa verifici amprenta OTR a persoanei de contact?</string> + <string name="pref_show_dynamic_tags">Arata etichetele dinamice</string> + <string name="pref_show_dynamic_tags_summary">Arata etichete de stare sub contacte </string> + <string name="enable_notifications">Activeaza notificari</string> + <string name="conference_with">Creeaza conferinta cu...</string> + <string name="no_conference_server_found">Serverul conferintei nu a fost gasita</string> + <string name="conference_creation_failed">Conferinta nu a putut fi creata!</string> + <string name="conference_created">Conferinta a fost creata!</string> + <string name="secret_accepted">Secret acceptat!</string> + <string name="reset">Reseteaza</string> + <string name="account_image_description">Avatar cont</string> + <string name="copy_otr_clipboard_description">Copiaza amprenta OTR in memorie</string> + <string name="copy_omemo_clipboard_description">Copiaza amprenta OMEMO in memorie</string> + <string name="regenerate_omemo_key">Genereaza din nou cheia OMEMO</string> + <string name="wipe_omemo_pep">Sterge alte dispozitive din PEP</string> + <string name="clear_other_devices">Curata lista dispozitive</string> + <string name="clear_other_devices_desc">Sigur vrei sa inlaturi toate celelalte dispozitive din mesajul de anunt OMEMO? Data viitoare cand dispozitivele se vor conecta, se vor anunta din nou, dar se poate ca ele sa nu fi primit mesajele trimise intre timp.</string> + <string name="purge_key">Sterge cheia</string> + <string name="purge_key_desc_part1">Sigur vrei sa stergi aceasta cheie?</string> + <string name="purge_key_desc_part2">Va fi considera compromisa ireversibil si nu vei mai putea crea o sesiune cu ea niciodata.</string> + <string name="error_no_keys_to_trust_server_error">Nu exista chei utilizabile pentru acest contact.\nIncercarea de a descarca o cheie de pe server a esuat. Se poate sa fie o problema cu serverul de contacte.</string> + <string name="error_no_keys_to_trust">Nu exista chei disponibile si utilizabile pentru acest contact. Daca ai sters vreuna din cheile contactului, trebuie sa generezi o alta noua.</string> + <string name="error_trustkeys_title">Eroare</string> + <string name="fetching_history_from_server">Descarc istoric de pe server</string> + <string name="no_more_history_on_server">Nu mai exista istoric pe server</string> + <string name="updating">Actualizez...</string> + <string name="password_changed">Parola schimbata</string> + <string name="could_not_change_password">Nu s-a putut schimba parola</string> + <string name="otr_session_not_started">Trimite un mesaj pentru a pornii o discutie criptata</string> + <string name="ask_question">Intreaba</string> + <string name="smp_explain_question">Tu si contactul tau aveti un secret in comun pe care nimeni altcineva nu il stie (cum ar fi o gluma sau ce ati mancat ultima data cand v-ati vazut) poti folosi acel secret ca sa va verificati amprenta fiecaruia.\n\nTu pui la dispozitie un indiciu sau o intrebare contactului iar el/ea va raspunde cu un mesaj sensibil la majuscule.</string> + <string name="smp_explain_answer">Contactul tau doreste sa iti verifice amprenta provocandu-te cu un secret partajat. Contactul tau a furnizat urmatorul indiciu sau intrebare legat de acel secret.</string> + <string name="shared_secret_hint_should_not_be_empty">Indiciul tau nu ar trebui sa fie gol</string> + <string name="shared_secret_can_not_be_empty">Secretul tau impartasit nu poate fi gol</string> + <string name="manual_verification_explanation">Compara cu grija amprenta afisata mai jos cu amprenta contactului.\nPoti folosi orice forma de comunicare de incredere precum un e-mail criptat sau o convorbire telefonica pentru a face schimb de amprente.</string> + <string name="change_password">Schimba parola</string> + <string name="current_password">Parola curenta</string> + <string name="new_password">Parola noua</string> + <string name="password_should_not_be_empty">Parola nu trebuie sa fie goala</string> + <string name="enable_all_accounts">Activeaza toate conturile</string> + <string name="disable_all_accounts">Dezactiveaza toate conturile</string> + <string name="perform_action_with">Efectuaza actiune cu</string> + <string name="no_affiliation">Fara afiliere</string> + <string name="no_role">Fara rol</string> + <string name="outcast">proscris</string> + <string name="member">Membru</string> + <string name="advanced_mode">Mod avansat</string> + <string name="grant_membership">Acorda calitatea de membru</string> + <string name="remove_membership">Abroga calitatea de membru</string> + <string name="grant_admin_privileges">Acorda privilegii de administrator</string> + <string name="remove_admin_privileges">Abroga privilegii de administrator</string> + <string name="remove_from_room">Inlatura din conferinta</string> + <string name="could_not_change_affiliation">Nu s-a putut schimba afilierea lui %s</string> + <string name="ban_from_conference">Interzice accesul la conferinta</string> + <string name="removing_from_public_conference">Incerci sa il inlaturi pe %s dintr-o conferinta publica. Singurul mod in care poti face asta este sa in blochezi pentru totdeauna.</string> + <string name="ban_now">Interzice accesul acum</string> + <string name="could_not_change_role">Nu s-a putut schimba rolul lui %s</string> + <string name="public_conference">Conferinta accesibila public</string> + <string name="private_conference">Conferinta privata, accesibila numai membrilor</string> + <string name="conference_options">Optiuni conferinta</string> + <string name="members_only">Privat, numai pentru membri</string> + <string name="non_anonymous">Ne anonim</string> + <string name="moderated">Monitorizata</string> + <string name="you_are_not_participating">Tu nu participi</string> + <string name="modified_conference_options">Optiuni conferinta modificate!</string> + <string name="could_not_modify_conference_options">Nu s-au putut modifca optiunile conferintei</string> + <string name="never">Niciodata</string> + <string name="thirty_minutes">30 minute</string> + <string name="one_hour">O ora</string> + <string name="two_hours">2 ore</string> + <string name="eight_hours">8 ore</string> + <string name="until_further_notice">Pana la noi ordine</string> + <string name="pref_input_options">Optiuni introducere</string> + <string name="pref_enter_is_send">ENTER trimite</string> + <string name="pref_enter_is_send_summary">Apasa tasta ENTER pentru a trimite mesajul</string> + <string name="pref_display_enter_key">Arata tasta ENTER</string> + <string name="pref_display_enter_key_summary">Preschimba tasta de zambilici in ENTER</string> + <string name="audio">audio</string> + <string name="video">video</string> + <string name="image">imagine</string> + <string name="pdf_document">document PDF</string> + <string name="apk">Aplicatie Android</string> + <string name="vcard">Contact</string> + <string name="received_x_file">Primit %s</string> + <string name="disable_foreground_service">Dezactiveaza serviciul in prim plan</string> + <string name="touch_to_open_conversations">Atinge pentru a deschide Conversations</string> + <string name="avatar_has_been_published">Avatarul a fost publicat!</string> + <string name="sending_x_file">Trimit %s</string> + <string name="offering_x_file">Ofer %s</string> + <string name="hide_offline">Ascunde deconectat</string> + <string name="disable_account">Dezactiveaza cont</string> + <string name="contact_is_typing">%s tasteaza...</string> + <string name="contact_has_stopped_typing">%s s-a oprit din scris</string> + <string name="pref_chat_states">Notificari cand cineva scrie</string> + <string name="pref_chat_states_summary">Anunta contactul cand scrii un nou mesaj</string> + <string name="send_location">Trimite locatia</string> + <string name="show_location">Arata locatia</string> + <string name="no_application_found_to_display_location">Nu s-a gasit nici o aplicatie care sa afiseze locatia</string> + <string name="location">Locatie</string> + <string name="received_location">Locatie primita</string> + <string name="title_undo_swipe_out_conversation">Conversatie inchisa</string> + <string name="title_undo_swipe_out_muc">A parasit conferinta</string> + <string name="pref_dont_trust_system_cas_title">Nu ai incredere in CA din sistem</string> + <string name="pref_dont_trust_system_cas_summary">Toate certificatele trebuie aprobate manual</string> + <string name="pref_remove_trusted_certificates_title">Inlatura certificatele</string> + <string name="pref_remove_trusted_certificates_summary">Sterge certificate aprobate manual</string> + <string name="toast_no_trusted_certs">Nici un certificat aprobat manual</string> + <string name="dialog_manage_certs_title">Inlatura certificatele</string> + <string name="dialog_manage_certs_positivebutton">Sterge selectia</string> + <string name="dialog_manage_certs_negativebutton">Anuleaza</string> + <plurals name="toast_delete_certificates"> + <item quantity="one">%d certificat sters</item> + <item quantity="few">%d certificate sterse</item> + <item quantity="other">%d certificate sterse</item> + </plurals> + <plurals name="select_contact"> + <item quantity="one">Selecteaza %d contact</item> + <item quantity="few">Selecteaza %d contacte</item> + <item quantity="other">Selecteaza %d contacte</item> + </plurals> + <string name="pref_quick_action_summary">Inlocuieste butonul de trimitere cu o actiune rapida</string> + <string name="pref_quick_action">Actiune rapida</string> + <string name="none">Nimic</string> + <string name="recently_used">Folosit recent</string> + <string name="choose_quick_action">Alege actiunea rapida</string> + <string name="search_for_contacts_or_groups">Cauta contacte sau grupuri</string> + <string name="send_private_message">trimite mesaj privat</string> + <string name="user_has_left_conference">%s a parasit conferinta!</string> + <string name="username">Nume utilizator</string> + <string name="username_hint">Nume utilizator</string> + <string name="invalid_username">Acesta nu este un nume de utilizator valabil</string> + <string name="download_failed_server_not_found">Descarcarea a esuat: Serverul nu a fost gasit</string> + <string name="download_failed_file_not_found">Descarcare esuata: Fisierul nu a fost gasit</string> + <string name="download_failed_could_not_connect">Descarcarea a esuat. Nu s-a putut realiza conexiunea cu gazda.</string> + <string name="account_status_tor_unavailable">Reteaua Tor nu este disponibila</string> + <string name="server_info_broken">Deteriorat</string> + <string name="pref_presence_settings">Setari de prezenta</string> + <string name="pref_away_when_screen_off">Plecat cand ecranul este oprit</string> + <string name="pref_away_when_screen_off_summary">Marcheaza clientul drept plecat cand ecranul este oprit</string> + <string name="pref_xa_on_silent_mode">Indisponibil in mod silentios</string> + <string name="pref_xa_on_silent_mode_summary">Declara clientul drept indisponibil atunci cand dispozitivul este in mod silentios</string> + <string name="pref_show_connection_options">Optiuni avansate conexiune</string> + <string name="pref_show_connection_options_summary">Arata optiunea de setare a numelui de gazda si a portului atunci cand se configureaza un cont</string> + <string name="hostname_example">xmpp.example.com</string> + <string name="action_add_account_with_certificate">Adauga un cont cu certificat</string> + <string name="unable_to_parse_certificate">Nu se poate analiza certificatul</string> + <string name="authenticate_with_certificate">Lasa gol pentru a autentifica cu un certificat</string> + <string name="mam_prefs">Preferinte arhivare</string> + <string name="server_side_mam_prefs">Preferinte arhivare pe server</string> + <string name="fetching_mam_prefs">Descarc preferinte arhivare. Va rugam asteptati...</string> + <string name="unable_to_fetch_mam_prefs">Nu s-au putut descarca preferintele de arhivare</string> + <string name="captcha_ocr">Text captcha de verificare</string> + <string name="captcha_required">Text captcha de verificare necesar</string> + <string name="captcha_hint">introdu textul din imagine</string> + <string name="certificate_chain_is_not_trusted">Seria de certificate nu este de incredere</string> + <string name="jid_does_not_match_certificate">ID-ul Jabber nu corespunde cu certificatul</string> + <string name="action_renew_certificate">Innoieste certificatul</string> + <string name="error_fetching_omemo_key">Eroare la preluarea cheii OMEMO!</string> + <string name="verified_omemo_key_with_certificate">Verifica cheia OMEMO cu un certificat</string> + <string name="device_does_not_support_certificates">Dispozitivul nu permite selectia unui certificat pentru client!</string> + <string name="pref_connection_options">Optiuni conexiune</string> + <string name="account_settings_hostname">Nume gazda</string> + <string name="account_settings_port">Port</string> + <string name="not_a_valid_port">Acesta nu este un numar de port valabil</string> + <string name="not_valid_hostname">Acesta nu este un nume de gazda valabila</string> + <string name="connected_accounts">%1$d din %2$d conturi conectate</string> + <plurals name="x_messages"> + <item quantity="one">%d mesaj</item> + <item quantity="few">%d mesaje</item> + <item quantity="other">%d mesaje</item> + </plurals> + <string name="shared_file_with_x">Partajeaza fisierul cu %s...</string> + <string name="shared_image_with_x">Partajeaza imaginea cu %s.</string> + <string name="no_storage_permission">Conversations are nevoie de acces la stocarea externa</string> + <string name="sync_with_contacts">Sincronizeaza cu contactele</string> + <string name="sync_with_contacts_long">Conversations doreste sa potriveasta lista de contacte XMPP cu cea din dispozitiv pentru a putea afisa numule lor complete si avatarele.\n\nConversations doar v-a citi si potrivi local fara sa le incarce catre vreun server.\n\nUrmeaza sa fii intrebat daca doresti sa permiti accesul la contacte.</string> + <string name="certificate_information">Informatii despre certificat</string> + <string name="certificate_subject">Subiect</string> + <string name="certificate_issuer"> +Emitent</string> + <string name="certificate_cn">Nume comun</string> + <string name="certificate_o">Organizatie</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(Indisponibil)</string> + <string name="certificate_not_found">Certificat inexistent</string> + <string name="notify_on_all_messages">Notifica la toate mesajele</string> + <string name="notify_only_when_highlighted">Notifica numai la evidentiere</string> + <string name="notify_never">Notificari dezactivate</string> + <string name="notify_paused">Notificari suspendate</string> + <string name="always">Mereu</string> + <string name="automatically">Automat</string> + <string name="battery_optimizations_enabled">Optimizare baterie activata</string> + <string name="battery_optimizations_enabled_explained">Dispozitivul dumneavoastra incearca sa optimizeze agresiv consumul bateriei pentru Conversations, asta poate duce la notificari intarziate sau chiar pierderi de mesaje.\nEste recomandat sa dezactivati aceste optimizari.</string> + <string name="battery_optimizations_enabled_dialog">Dispozitivul dumneavoastra incearca sa optimizeze agresiv consumul bateriei pentru Conversations, asta poate duce la notificari intarziate sau chiar pierderi de mesaje.\nIn continuare veti fi rugat sa dezactivati aceste optimizari.</string> + <string name="disable">Dezactivat</string> + <string name="selection_too_large">Zona selectata este prea mare</string> + <string name="no_accounts">(Nici un cont activat)</string> + <string name="this_field_is_required">Acest camp este obligatoriu</string> </resources> diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 3a2515b7..e2ef9436 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Защищенная беседа</string> <string name="action_add_account">Добавить аккаунт</string> <string name="action_edit_contact">Редактировать контакт</string> - <string name="action_add_phone_book">Добавить в телефонную книгу</string> + <string name="action_add_phone_book">Добавить в адресную книгу</string> <string name="action_delete_contact">Удалить из списка</string> <string name="action_block_contact">Заблокировать контакт</string> <string name="action_unblock_contact">Разблокировать контакт</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d мин. назад</string> <string name="unread_conversations">непрочитанных сообщений</string> <string name="sending">отправка…</string> - <string name="encrypted_message">Расшифровка сообщения. Пожалуйста, подождите…</string> + <string name="message_decrypting">Расшифровка сообщения. Подождите...</string> + <string name="pgp_message">OpenPGP зашифрованное сообщение</string> <string name="nick_in_use">Имя уже используется</string> <string name="admin">Администратор</string> <string name="owner">Владелец</string> @@ -74,10 +75,12 @@ <string name="clear_conversation_history">Очистить историю</string> <string name="clear_histor_msg">Вы хотите удалить все сообщения в этой беседе?\n\n<b>Предупреждение:</b> Данная операция не повлияет на сообщения, хранящиеся на других устройствах.</string> <string name="delete_messages">Удалить сообщения</string> - <string name="also_end_conversation">Завершить беседу</string> + <string name="also_end_conversation">Закончить эту беседу впоследствии</string> <string name="choose_presence">Укажите статус для контакта</string> - <string name="send_plain_text_message">Отправить незашифрованное текстовое сообщение</string> + <string name="send_unencrypted_message">Отправить незащифрованное сообщение</string> <string name="send_otr_message">Отправить OTR защифрованное сообщение</string> + <string name="send_omemo_message">Отправить OMEMO защифрованное сообщение</string> + <string name="send_omemo_x509_message">Послать v\\OMEMO зашифрованное сообщение</string> <string name="send_pgp_message">Отправить OpenPGP защифрованное сообщение</string> <string name="your_nick_has_been_changed">Ваш псевдоним был изменен</string> <string name="send_unencrypted">Отправить в незашифрованном виде</string> @@ -86,35 +89,31 @@ <string name="openkeychain_required_long">Conversations использует стороннее приложение под названием <b>OpenKeychain</b> для шифрования и расшифрования сообщений и управления открытыми ключами.\nПрограмма OpenKeychain распространяется под лицензией GPLv3 и доступна для загрузки через F-Droid или Google Play.\n\n<small>(Потребуется перезапуск Conversations после установки.)</small></string> <string name="restart">Перезапуск</string> <string name="install">Установка</string> + <string name="openkeychain_not_installed">Установите OpenKeychain пожалуйста</string> <string name="offering">предложение…</string> <string name="waiting">ожидание…</string> <string name="no_pgp_key">Нет OpenPGP ключа</string> <string name="contact_has_no_pgp_key">Conversations не может зашифровать сообщение, потому что удаленный пользователь не анонсирует свой открытый ключ.\n\n<small>Пожалуйста, попросите удаленного пользователя тоже установить OpenPGP.</small></string> <string name="no_pgp_keys">Нет OpenPGP ключей</string> <string name="contacts_have_no_pgp_keys">Conversations не может зашифровать сообщения, потому что удаленные пользователи не анонсируют свои открытые ключи.\n\n<small>Пожалуйста, попросите удаленных пользователей тоже установить OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Зашифрованное сообщение получено. Нажмите здесь, чтобы расшифровать и посмотреть сообщение.</i></string> + <string name="encrypted_message_received"><i>Получено зашифрованное сообщение. Нажмите, чтобы расшифровать.</i></string> <string name="pref_general">Общие</string> <string name="pref_xmpp_resource">Название ресурса</string> <string name="pref_xmpp_resource_summary">Имя которым Conversations идентифицирует себя</string> <string name="pref_accept_files">Принимать файлы</string> - <string name="pref_accept_files_size_summary">Автоматический прием файлов…</string> - <string name="pref_notification_settings">Настройки Уведомлений</string> + <string name="pref_accept_files_summary">Автоматический прием файлов…</string> <string name="pref_notifications">Уведомление</string> <string name="pref_notifications_summary">Использовать звуковое уведомление когда приходят новые сообщения</string> <string name="pref_vibrate">Вибрация</string> <string name="pref_vibrate_summary">Использовать вибрацию когда приходят новые сообщения</string> <string name="pref_sound">Звуковой сигнал</string> <string name="pref_sound_summary">Выберите звуковой сигнал для сообщений</string> - <string name="pref_conference_notifications">Уведомления конференции</string> - <string name="pref_conference_notifications_summary">Всегда сообщать при получении нового сообщения в конференции</string> <string name="pref_notification_grace_period">Отсрочка уведомлений</string> <string name="pref_notification_grace_period_summary">Не использовать уведомления, если вы прочитали сообщение на другом устройстве</string> - <string name="pref_advanced_options">Дополнительные параметры</string> <string name="pref_never_send_crash">Отчеты об ошибках</string> <string name="pref_never_send_crash_summary">Отправляя отчеты об ошибках, вы помогаете исправить и улучшить Conversations, поддерживая дальнейшее развитие программы</string> <string name="pref_confirm_messages">Отчеты о получении</string> <string name="pref_confirm_messages_summary">Разрешить уведомлять отправителя, когда вы получили и прочитали сообщение</string> - <string name="pref_ui_options">Параметры интерфейса</string> <string name="openpgp_error">Возникла ошибка в OpenKeychain</string> <string name="error_decrypting_file">Ошибка расшифровки файла</string> <string name="accept">Принять</string> @@ -149,9 +148,10 @@ <string name="account_status_regis_not_sup">Сервер не поддерживает регистрацию</string> <string name="account_status_security_error">Ошибка безопасности</string> <string name="account_status_incompatible_server">Несовместимый сервер</string> - <string name="encryption_choice_none">Без шифрования</string> + <string name="encryption_choice_unencrypted">Без шифра</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Редактировать аккаунт</string> <string name="mgmt_account_delete">Удалить</string> <string name="mgmt_account_disable">Отключить</string> @@ -170,7 +170,7 @@ <string name="passwords_do_not_match">Пароли не совпадают</string> <string name="invalid_jid">Недопустимый JID (Джаббер ID)</string> <string name="error_out_of_memory">Недостаточно памяти. Изображение слишком большое</string> - <string name="add_phone_book_text">Вы хотите добавить %s в свою телефонную книгу?</string> + <string name="add_phone_book_text">Вы хотите добавить %s в вашу адресную книгу?</string> <string name="contact_status_online">в сети</string> <string name="contact_status_free_to_chat">свободен для общения</string> <string name="contact_status_away">скоро буду</string> @@ -186,7 +186,8 @@ <string name="server_info_blocking">XEP-0191: Команда Блокирования</string> <string name="server_info_roster_version">XEP-0237: Управление версиями списков</string> <string name="server_info_stream_management">XEP-0198: Управление потоками</string> - <string name="server_info_pep">XEP-0163: PEP (Аватары)</string> + <string name="server_info_pep">XEP-0163: PEP (Аватары / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: Загрузка файлов по HTTP</string> <string name="server_info_available">доступен</string> <string name="server_info_unavailable">недоступен</string> <string name="missing_public_keys">Отсутствие анонсирования открытых ключей</string> @@ -204,17 +205,28 @@ <string name="reception_failed">Прием не удался</string> <string name="your_fingerprint">Контрольная сумма</string> <string name="otr_fingerprint">OTR контрольная сумма</string> + <string name="omemo_fingerprint">OMEMO отпечаток</string> + <string name="omemo_fingerprint_x509">v\\OMEMO отпечаток</string> + <string name="omemo_fingerprint_selected_message">OMEMO отпечаток сообщения</string> + <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO отпечаток сообщения</string> + <string name="this_device_omemo_fingerprint">Собственный OMEMO отпечаток</string> + <string name="other_devices">Другие устройства</string> + <string name="trust_omemo_fingerprints">Доверенные отпечатки OMEMO</string> + <string name="fetching_keys">Получение ключей…</string> + <string name="done">Готово</string> <string name="verify">Подтвердить</string> <string name="decrypt">Дешифровать</string> <string name="conferences">Конференции</string> <string name="search">Поиск</string> <string name="create_contact">Создать контакт</string> + <string name="enter_contact">Добавить контакт</string> <string name="join_conference">Присоединиться к конференции</string> <string name="delete_contact">Удалить Контакт</string> <string name="view_contact_details">Посмотреть данные контакта</string> <string name="block_contact">Заблокировать контакт</string> <string name="unblock_contact">Разблокировать контакт</string> <string name="create">Создать</string> + <string name="select">Select</string> <string name="contact_already_exists">Контакт уже существует</string> <string name="join">Присоединиться</string> <string name="conference_address">Адрес конференции</string> @@ -249,7 +261,6 @@ <string name="skip">Пропустить</string> <string name="disable_notifications">Отключить уведомления</string> <string name="disable_notifications_for_this_conversation">Отключить уведомления для текущей беседы</string> - <string name="notifications_disabled">Уведомления отключены</string> <string name="enable">Включить</string> <string name="conference_requires_password">Конференция требует авторизации</string> <string name="enter_password">Введите пароль</string> @@ -260,12 +271,10 @@ <string name="sure_delete_fingerprint">Вы уверены, что хотите удалить данную контрольную сумму?</string> <string name="ignore">Отменить</string> <string name="without_mutual_presence_updates"><b>Внимание:</b> Если обновления присутствия не включены на обеих сторонах, это может привести к возникновению неожиданных проблемам.\n\n<small>Уточните сведения о контакте, проверив настройки обновлений присутствия.</small></string> - <string name="pref_encryption_settings">Настройки шифрования</string> <string name="pref_force_encryption">Обязательное сквозное шифрование</string> <string name="pref_force_encryption_summary">Всегда отправлять сообщения зашифрованными (за исключением конференций)</string> <string name="pref_dont_save_encrypted">Не сохранять зашифрованные сообщения</string> <string name="pref_dont_save_encrypted_summary">Внимание: Это может привести к потере сообщений</string> - <string name="pref_expert_options">Расширенные настройки</string> <string name="pref_expert_options_summary">Пожалуйста, будьте осторожны с данными настройками</string> <string name="title_activity_about">О Conversations</string> <string name="pref_about_conversations_summary">Информация о билде и лицензировании</string> @@ -284,15 +293,19 @@ <string name="pref_conference_name">Название конференции</string> <string name="pref_conference_name_summary">Использовать тему беседы заместо JID для отображения конференций</string> <string name="toast_message_otr_fingerprint">OTR-отпечаток скопирован в буфер обмена!</string> + <string name="toast_message_omemo_fingerprint">OMEMO отпечаток скопирован в буфер обмена!</string> <string name="conference_banned">Вы заблокированы в этой конференции</string> <string name="conference_members_only">Эта конференция требует членства</string> <string name="conference_kicked">Вы были удалены из конференции</string> <string name="using_account">использовать учётную запись %s</string> + <string name="checking_x">Проверка %s на сервере HTTP</string> <string name="not_connected_try_again">Вы неподключены. Попробуйте позже</string> + <string name="check_x_filesize">Проверьте размер %s</string> <string name="message_options">Опции сообщения</string> <string name="copy_text">Копировать текст</string> <string name="copy_original_url">Копировать адрес ссылки</string> <string name="send_again">Отправить ещё раз</string> + <string name="file_url">URL файла</string> <string name="message_text">Текст сообщения</string> <string name="url_copied_to_clipboard">Ссылка скопирована в буфер обмена</string> <string name="message_copied_to_clipboard">Сообщение скопировано в буфер обмена</string> @@ -304,7 +317,6 @@ <string name="verify_otr">Подтвердить OTR</string> <string name="remote_fingerprint">Удалённый отпечаток</string> <string name="scan">поиск</string> - <string name="or_touch_phones">(или сенсорные телефоны)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Подсказка или вопрос</string> <string name="shared_secret_secret">Общий секретный ключ</string> @@ -321,6 +333,9 @@ <string name="conversations_foreground_service">Диалоги</string> <string name="pref_keep_foreground_service">Оставить службу на переднем плане</string> <string name="pref_keep_foreground_service_summary">Не позволяет операционной системе закрыть ваше соединение</string> + <string name="pref_export_logs">Экспорт истории</string> + <string name="pref_export_logs_summary">Записать историю на SD карту</string> + <string name="notification_export_logs_title">Запись истории на SD карту</string> <string name="choose_file">Выберите файл</string> <string name="receiving_x_file">Получение %1$s (%2$d%% выполнено)</string> <string name="download_x_file">Загружено %s</string> @@ -347,6 +362,16 @@ <string name="reset">Сброс</string> <string name="account_image_description">Изображение учётной записи</string> <string name="copy_otr_clipboard_description">Скопировать OTR-отпечаток в буфер обмена</string> + <string name="copy_omemo_clipboard_description">Скопировать OMEMO-отпечаток в буфер обмена</string> + <string name="regenerate_omemo_key">Создать заново ключ OMEMO</string> + <string name="wipe_omemo_pep">Стереть другие устройства из PEP</string> + <string name="clear_other_devices">Очистить устройства</string> + <string name="clear_other_devices_desc">Вы уверены, что хотите очистить все остальные устройства из анонса ключей OMEMO? При соединении устройств в следующий раз новые ключи анонсируются автоматически, но устройства могут не получить сообщения, посланные до этого.</string> + <string name="purge_key">Очистить ключ</string> + <string name="purge_key_desc_part1">Вы уверены, что хотите удалить этот ключ?</string> + <string name="purge_key_desc_part2">Он будет необратимо считаться скомпрометированным, и вы никогда не сможете создать сессию с ним снова.</string> + <string name="error_no_keys_to_trust">Для этого контакта не существует доступных ключей. Если вы очистили все его ключи, ему необходимо создать новые.</string> + <string name="error_trustkeys_title">Ошибка</string> <string name="fetching_history_from_server">Получение истории с сервера</string> <string name="no_more_history_on_server">На сервере больше нет истории</string> <string name="updating">Обновление...</string> @@ -384,8 +409,10 @@ <string name="public_conference">Публичная конференция</string> <string name="private_conference">Приватная конференция только для членов</string> <string name="conference_options">Настройки конференции</string> - <string name="members_only">Закрытый доступ (только для участников)</string> + <string name="members_only">Частная, только для участников</string> <string name="non_anonymous">Не анонимно </string> + <string name="moderated">Модерируемая</string> + <string name="you_are_not_participating">Вы не участвуете</string> <string name="modified_conference_options">Настройки конференции изменены!</string> <string name="could_not_modify_conference_options">Не удалось изменить настройки конференции</string> <string name="never">Никогда</string> @@ -394,7 +421,6 @@ <string name="two_hours">2 часа</string> <string name="eight_hours">8 часов</string> <string name="until_further_notice">До следующего уведомления</string> - <string name="pref_input_options">Настройки ввода</string> <string name="pref_enter_is_send">Отправить на \"Enter\"</string> <string name="pref_enter_is_send_summary">Клавиша \"Enter\" отправляет сообщение</string> <string name="pref_display_enter_key">Показывать клавишу ввода</string> @@ -413,7 +439,7 @@ <string name="offering_x_file">Предложен %s</string> <string name="hide_offline">Скрыть пользователей вне сети</string> <string name="disable_account">Отключить учётную запись</string> - <string name="contact_is_typing">%s набирает сообщение...</string> + <string name="contact_is_typing">%s печатает…</string> <string name="contact_has_stopped_typing">%s прекратил набор</string> <string name="pref_chat_states">Оповещения о наборе</string> <string name="pref_chat_states_summary">Позволяет вашим контактам видеть когда вы пишете новое сообщение</string> @@ -424,7 +450,6 @@ <string name="received_location">Получено местоположение</string> <string name="title_undo_swipe_out_conversation">Беседа окончена</string> <string name="title_undo_swipe_out_muc">Покинул беседу</string> - <string name="pref_certificate_options">Опции сертификата</string> <string name="pref_dont_trust_system_cas_title">Не доверять системным УЦ</string> <string name="pref_dont_trust_system_cas_summary">Все сертификаты должны быть подтверждены вручную</string> <string name="pref_remove_trusted_certificates_title">Удалить сертификат</string> @@ -436,13 +461,13 @@ <plurals name="toast_delete_certificates"> <item quantity="one">Удалён %d сертификат</item> <item quantity="few">Удалено %d сертификатов</item> - <item quantity="many"></item> + <item quantity="many">Удалено %d сертификатов</item> <item quantity="other">Удалено %d сертификатов</item> </plurals> <plurals name="select_contact"> <item quantity="one">Выбран %d контакт</item> <item quantity="few">Выбрано %d контактов</item> - <item quantity="many"></item> + <item quantity="many">Выбрано %d контактов</item> <item quantity="other">Выбрано %d контактов</item> </plurals> <string name="pref_quick_action_summary">Заменить кнопку отправки кнопкой быстрого действия</string> @@ -450,4 +475,54 @@ <string name="none">Нет</string> <string name="recently_used">Последнее выбранное</string> <string name="choose_quick_action">Выбрать быстрое действие</string> + <string name="search_for_contacts_or_groups">Поиск контактов или групп</string> + <string name="send_private_message">Отправить частное сообщение</string> + <string name="user_has_left_conference">%s покинул конференцию!</string> + <string name="username">Имя пользователя</string> + <string name="username_hint">Имя пользователя</string> + <string name="invalid_username">Недопустимое имя пользователя</string> + <string name="download_failed_server_not_found">Загрузка не удалась: сервер не найден</string> + <string name="download_failed_file_not_found">Загрузка не удалась: файл не найден</string> + <string name="download_failed_could_not_connect">Загрузка не удалась: не удалось подключиться к серверу</string> + <string name="account_status_tor_unavailable">Сеть Tor недоступна</string> + <string name="server_info_broken">Повреждено</string> + <string name="pref_away_when_screen_off">Вышел когда экран выключен</string> + <string name="pref_away_when_screen_off_summary">Отмечает ваш ресурс как \"вышел\" когда экран выключен</string> + <string name="pref_xa_on_silent_mode">Не доступен в режиме без звука</string> + <string name="action_add_account_with_certificate">Добавить аккаунт с сертификатом</string> + <string name="unable_to_parse_certificate">Невозможно разобрать сертификат</string> + <string name="authenticate_with_certificate">Оставить пустым для входа с сертификатом</string> + <string name="captcha_ocr">Проверочный текст</string> + <string name="captcha_required">Необходима проверка</string> + <string name="captcha_hint">введите текст с картинки</string> + <string name="certificate_chain_is_not_trusted">Цепочка сертификата не доверена</string> + <string name="jid_does_not_match_certificate">Jabber ID не соответствует сертификату</string> + <string name="action_renew_certificate">Обновить сертификат</string> + <string name="error_fetching_omemo_key">Ошибка при получении OMEMO ключа!</string> + <string name="verified_omemo_key_with_certificate">Проверен OMEMO ключ с сертификатом!</string> + <string name="device_does_not_support_certificates">Ваше устройство не поддерживает выбор клиентских сертификатов!</string> + <string name="account_settings_hostname">Имя сервера</string> + <string name="account_settings_port">Порт</string> + <string name="not_a_valid_port">Это недопустимый номер порта</string> + <string name="not_valid_hostname">Это недопустимое имя сервера</string> + <string name="connected_accounts">%1$d из %2$d аккаунтов соединены</string> + <string name="shared_file_with_x">Общий файл с %s</string> + <string name="shared_image_with_x">Общее изображение с %s</string> + <string name="no_storage_permission">Conversations требуется доступ к внешнему хранилищу</string> + <string name="sync_with_contacts">Синхронизировать с контактами</string> + <string name="sync_with_contacts_long">Conversations ожидает совпадения вашего XMPP ростера с вашими контактами для отображения их полных имен и аватаров.\n\nConversations только считает ваши контакты и сопоставит их локально без отправки их на ваш сервер.\n\nСейчас вы получите запрос на разрешение доступа к вашим контактам.</string> + <string name="certificate_information">Информация о сертификате</string> + <string name="certificate_subject">Subject</string> + <string name="certificate_issuer">Выпустил</string> + <string name="certificate_o">Организация</string> + <string name="certicate_info_not_available">(недоступно)</string> + <string name="certificate_not_found">Сертификатов не найдено</string> + <string name="notify_on_all_messages">Уведомлять о всех сообщениях</string> + <string name="notify_never">Уведомления запрещены</string> + <string name="notify_paused">Уведомления приостановлены</string> + <string name="always">Всегда</string> + <string name="automatically">Автоматически</string> + <string name="battery_optimizations_enabled">Оптимизации энергопотребления разрешены</string> + <string name="disable">Запретить</string> + <string name="selection_too_large">Выбранная область слишком большая</string> </resources> diff --git a/src/main/res/values-sk/strings.xml b/src/main/res/values-sk/strings.xml index 01211ad2..ce99da94 100644 --- a/src/main/res/values-sk/strings.xml +++ b/src/main/res/values-sk/strings.xml @@ -9,7 +9,6 @@ <string name="action_secure">Zabezpečená konverzácia</string> <string name="action_add_account">Pridať účet</string> <string name="action_edit_contact">Upraviť meno</string> - <string name="action_add_phone_book">Pridať do telefónneho zoznamu</string> <string name="action_delete_contact">Vymazať zo zoznamu</string> <string name="action_block_contact">Zablokovať kontakt</string> <string name="action_unblock_contact">Odblokovať kontakt</string> @@ -27,8 +26,7 @@ <string name="minute_ago">pred 1 minútou</string> <string name="minutes_ago">pred %d minútami</string> <string name="unread_conversations">neprečítané konverzácie</string> - <string name="sending">odosielam…</string> - <string name="encrypted_message">Dešifrujem správu. Čakajte, prosím…</string> + <string name="sending">posielam...</string> <string name="nick_in_use">Prezývka už existuje</string> <string name="admin">Administrátor</string> <string name="owner">Vlastník</string> @@ -74,10 +72,11 @@ <string name="clear_conversation_history">Vymazať históriu konverzácií</string> <string name="clear_histor_msg">Chcete vymazať všetky správy v tejto konverzácii?\n\n<b>Varovanie:</b> Toto neovplyvní správy uložené v iných zariadeniach alebo serveroch.</string> <string name="delete_messages">Vymazať správy</string> - <string name="also_end_conversation">Následne ukončiť aj túto konverzáciu</string> + <string name="also_end_conversation">Neskôr ukončiť konverzáciu</string> <string name="choose_presence">Vybrať aktualizáciu stavu pre kontakt</string> - <string name="send_plain_text_message">Poslať textovú správu</string> + <string name="send_unencrypted_message">Poslať nezašifrovanú správu</string> <string name="send_otr_message">Poslať OTR šifrovanú správu</string> + <string name="send_omemo_message">Poslať OMEMO šifrovanú správu</string> <string name="send_pgp_message">Poslať OpenPGP šifrovanú správu</string> <string name="your_nick_has_been_changed">Prezývka sa zmenila</string> <string name="send_unencrypted">Poslať nešifrované</string> @@ -92,29 +91,23 @@ <string name="contact_has_no_pgp_key">Nie je možné zašifrovať správu v aplikácii Conversations, pretože druhá strana neoznamuje svoj verejný kľúč.\n\n<small>Požiadajte svoj kontakt o nastavenie OpenPGP.</small></string> <string name="no_pgp_keys">Nenašli sa žiadne OpenPGP kľúče</string> <string name="contacts_have_no_pgp_keys">Nie je možné zašifrovať správy v aplikácii Conversations, pretože kontakty neoznamujú svoj verejný kľúč.\n\n<small>Požiadajte svoje kontakty o nastavenie OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Bola prijatá šifrovaná správa. Kliknite pre dešifrovanie a prečítanie.</i></string> <string name="pref_general">Všeobecné</string> <string name="pref_xmpp_resource">XMPP zdroj</string> <string name="pref_xmpp_resource_summary">Meno, ktorým sa tento klient identifikuje</string> <string name="pref_accept_files">Prijať súbory</string> - <string name="pref_accept_files_size_summary">Automaticky prijať súbory menšie ako…</string> - <string name="pref_notification_settings">Nastavenia upozornení</string> + <string name="pref_accept_files_summary">Automaticky prijať súbory menšie ako…</string> <string name="pref_notifications">Upozornenia</string> <string name="pref_notifications_summary">Upozorniť pri prijatí novej správy</string> <string name="pref_vibrate">Vibrovať</string> <string name="pref_vibrate_summary">Vibrovať pri prijatí novej správy</string> <string name="pref_sound">Zvuk</string> <string name="pref_sound_summary">Prehrať zvuk spolu s upozornením</string> - <string name="pref_conference_notifications">Upozornenia pri skupinovej konverzácii</string> - <string name="pref_conference_notifications_summary">Vždy upozorniť pri novej konferenčnej správe, nie len ak je zvýraznená</string> <string name="pref_notification_grace_period">Doba na prečítanie upozornenia</string> <string name="pref_notification_grace_period_summary">Neupozorňovať krátko po obdržaní kópie správy</string> - <string name="pref_advanced_options">Rozšírené možnosti</string> <string name="pref_never_send_crash">Neodosielať detaily o zlyhaní aplikácie</string> <string name="pref_never_send_crash_summary">Zaslaním detailov o dôvode zlyhania pomáhate ďalšiemu vývoju aplikácie Conversations</string> <string name="pref_confirm_messages">Potvrdzovať správy</string> <string name="pref_confirm_messages_summary">Oznámi kontaktom, že správa bola prijatá a prečítaná</string> - <string name="pref_ui_options">Možnosti UI</string> <string name="openpgp_error">OpenKeychain nahlásil chybu</string> <string name="error_decrypting_file">I/O chyba dešifrovania súboru</string> <string name="accept">Prijať</string> @@ -149,9 +142,10 @@ <string name="account_status_regis_not_sup">Server nepodporuje registráciu</string> <string name="account_status_security_error">Bezpečnostná chyba</string> <string name="account_status_incompatible_server">Nekompatibilný server</string> - <string name="encryption_choice_none">Čistý text</string> + <string name="encryption_choice_unencrypted">Nezašifrovaný</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Upraviť účet</string> <string name="mgmt_account_delete">Vymazať účet</string> <string name="mgmt_account_disable">Dočasne vypnúť</string> @@ -170,7 +164,6 @@ <string name="passwords_do_not_match">Heslá nezodpovedajú</string> <string name="invalid_jid">Toto nie je platné Jabber ID</string> <string name="error_out_of_memory">Nedostatok pamäti. Obrázok je príliš veľký</string> - <string name="add_phone_book_text">Chcete pridať %s do svojho telefónneho zoznamu?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">dostupný pre chat</string> <string name="contact_status_away">preč</string> @@ -186,7 +179,8 @@ <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> <string name="server_info_available">dostupný</string> <string name="server_info_unavailable">nedostupný</string> <string name="missing_public_keys">Chýba oznámenie o verejnom kľúči</string> @@ -204,6 +198,12 @@ <string name="reception_failed">Príjem zlyhal</string> <string name="your_fingerprint">Váš identifikátor</string> <string name="otr_fingerprint">OTR identifikátor</string> + <string name="omemo_fingerprint">OMEMO identifikátor</string> + <string name="omemo_fingerprint_selected_message">OMEMO identifkátor správy</string> + <string name="this_device_omemo_fingerprint">Vlastný OMEMO identifikátor</string> + <string name="other_devices">Ostatné zariadenia</string> + <string name="trust_omemo_fingerprints">Dôverovať OMEMO identifikátoru</string> + <string name="done">Dokončený</string> <string name="verify">Overiť</string> <string name="decrypt">Dešifrovať</string> <string name="conferences">Skupinové konverzácie</string> @@ -249,7 +249,6 @@ <string name="skip">Preskočiť</string> <string name="disable_notifications">Vypnúť upozornenia</string> <string name="disable_notifications_for_this_conversation">Vypnúť upozornenia pre túto konverzáciu</string> - <string name="notifications_disabled">Upozornenia sú vypnuté</string> <string name="enable">Povoliť</string> <string name="conference_requires_password">Skupinová konverzácia vyžaduje heslo</string> <string name="enter_password">Vložiť heslo</string> @@ -260,12 +259,10 @@ <string name="sure_delete_fingerprint">Naozaj chcete vymazať tento identifikátor?</string> <string name="ignore">Ignorovať</string> <string name="without_mutual_presence_updates"><b>Varovanie:</b> Odoslanie bez povolenia zmien stavu môže spôsobiť nečakané problémy na obidvoch stranách.\n\n<small>Prejdi na detaily kontaktu pre overenie povolenia o zmenách stavu.</small></string> - <string name="pref_encryption_settings">Nastavenie šifrovania</string> <string name="pref_force_encryption">Vynútiť šifrovanie</string> <string name="pref_force_encryption_summary">Vždy zasielať šifrované správy (okrem skupinových konverzácií)</string> <string name="pref_dont_save_encrypted">Neukladať šifrované správy</string> <string name="pref_dont_save_encrypted_summary">Varovanie: Toto môže viesť k strate správ</string> - <string name="pref_expert_options">Expertné možnosti</string> <string name="pref_expert_options_summary">S týmto narábajte veľmi opatrne, prosím</string> <string name="title_activity_about">O Conversations</string> <string name="pref_about_conversations_summary">Informácie o tvorbe a licencii</string> @@ -288,6 +285,7 @@ <string name="conference_members_only">Táto konverzácia je iba pre členov</string> <string name="conference_kicked">Vyčlenili vás z tejto konverzácie</string> <string name="using_account">Používa sa účet %s</string> + <string name="checking_x">Overiť %s na HTTP host</string> <string name="not_connected_try_again">Nie ste pripojený. Skúste to neskôr</string> <string name="check_x_filesize">Overiť %s veľkosť</string> <string name="message_options">Možnosti správy</string> @@ -306,7 +304,6 @@ <string name="verify_otr">Overiť OTR</string> <string name="remote_fingerprint">Vzdialený identifikátor</string> <string name="scan">skenovať</string> - <string name="or_touch_phones">(alebo dotykové telefóny)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Pomôcka alebo Otázka</string> <string name="shared_secret_secret">Spoločné tajomstvo</string> @@ -349,6 +346,8 @@ <string name="reset">Vymazať</string> <string name="account_image_description">Avatar účtu</string> <string name="copy_otr_clipboard_description">Skopírovať OTR identifikátor do schránky</string> + <string name="copy_omemo_clipboard_description">Skopírovať OMEMO identifikátor do schránky</string> + <string name="error_trustkeys_title">Chyba</string> <string name="fetching_history_from_server">Načítať históriu zo serveru</string> <string name="no_more_history_on_server">Na serveri nie je žiadna ďalšia história</string> <string name="updating">Aktualizujem...</string> @@ -386,8 +385,8 @@ <string name="public_conference">Verejne prístupná skupinová konverzácia</string> <string name="private_conference">Súkromná konverzácia iba pre členov</string> <string name="conference_options">Možnosti skupinovej konverzácie</string> - <string name="members_only">Súkromný (Iba pre členov)</string> <string name="non_anonymous">Neanonymný</string> + <string name="you_are_not_participating">Nezúčastňujete sa</string> <string name="modified_conference_options">Možnosti skupinovej konverzácie nastavené!</string> <string name="could_not_modify_conference_options">Nepodarilo sa nastaviť možnosti skupinovej konverzácie</string> <string name="never">Nikdy</string> @@ -396,7 +395,6 @@ <string name="two_hours">2 hodiny</string> <string name="eight_hours">8 hodín</string> <string name="until_further_notice">Až do odvolania</string> - <string name="pref_input_options">Možnosti zadávaní</string> <string name="pref_enter_is_send">Enter odosiela</string> <string name="pref_enter_is_send_summary">Použiť klávesu enter na odoslanie správy</string> <string name="pref_display_enter_key">Zobraziť klávesu enter</string> @@ -415,7 +413,6 @@ <string name="offering_x_file">Ponúkam %s</string> <string name="hide_offline">Skryť neprihlásených</string> <string name="disable_account">Vypnúť účet</string> - <string name="contact_is_typing">%s píše...</string> <string name="contact_has_stopped_typing">%s prestal písať</string> <string name="pref_chat_states">Upozornenia pri písaní</string> <string name="pref_chat_states_summary">Upozorniť kontakt, keď píšete novú správu</string> @@ -426,7 +423,6 @@ <string name="received_location">Prijatá poloha</string> <string name="title_undo_swipe_out_conversation">Konverzácia zatvorená</string> <string name="title_undo_swipe_out_muc">Opustil skupinovú konverzáciu</string> - <string name="pref_certificate_options">Možnosti certifikátu</string> <string name="pref_dont_trust_system_cas_title">Nedôverovať systému CAs</string> <string name="pref_dont_trust_system_cas_summary">Všetky certifikáty musia byť ručne schválené</string> <string name="pref_remove_trusted_certificates_title">Odstrániť certifikáty</string> @@ -450,5 +446,17 @@ <string name="none">Žiadny</string> <string name="recently_used">Naposledy použitý</string> <string name="choose_quick_action">Vybrať rýchlu voľbu</string> - <string name="file_not_found_on_remote_host">Súbor sa na vzdialenom serveri nenašiel</string> + <string name="search_for_contacts_or_groups">Hľadať kontakty alebo skupiny</string> + <string name="send_private_message">Poslať súkromnú správu</string> + <string name="user_has_left_conference">%s opustil skupinovú konverzáciu!</string> + <string name="username">Užívateľské meno</string> + <string name="username_hint">Užívateľské meno</string> + <string name="invalid_username">Toto nie je platné užívateľské meno</string> + <string name="pref_xa_on_silent_mode">Nedostupný v tichom režime</string> + <string name="action_add_account_with_certificate">Pridať účet s certifikátom</string> + <string name="unable_to_parse_certificate">Neschopný analyzovať certifikát</string> + <string name="captcha_ocr">Text CAPTCHA</string> + <string name="captcha_required">Potrebný CAPTCHA</string> + <string name="captcha_hint">vložiť text z obrázku</string> + <string name="action_renew_certificate">Obnoviť certifikát</string> </resources> diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml index 082cfa46..92f5a933 100644 --- a/src/main/res/values-sr/strings.xml +++ b/src/main/res/values-sr/strings.xml @@ -5,7 +5,7 @@ <string name="action_accounts">Управљај налозима</string> <string name="action_end_conversation">Окончај преписку</string> <string name="action_contact_details">Детаљи контакта</string> - <string name="action_muc_details">Детаљи конференције</string> + <string name="action_muc_details">Детаљи групног ћаскања</string> <string name="action_secure">Безбедна преписка</string> <string name="action_add_account">Додај налог</string> <string name="action_edit_contact">Уреди име</string> @@ -17,9 +17,9 @@ <string name="action_unblock_domain">Одблокирај домен</string> <string name="title_activity_manage_accounts">Управљање налозима</string> <string name="title_activity_settings">Поставке</string> - <string name="title_activity_conference_details">Детаљи конференције</string> + <string name="title_activity_conference_details">Детаљи групног ћаскања</string> <string name="title_activity_contact_details">Детаљи контакта</string> - <string name="title_activity_sharewith">Подели са преписком</string> + <string name="title_activity_sharewith">Подели у преписци</string> <string name="title_activity_start_conversation">Почни преписку</string> <string name="title_activity_choose_contact">Изабери контакт</string> <string name="title_activity_block_list">Списак блокираних</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">пре %d минута</string> <string name="unread_conversations">непрочитане преписке</string> <string name="sending">шаљем…</string> - <string name="encrypted_message">Дешифрујем поруку, сачекајте…</string> + <string name="message_decrypting">Дешифрујем поруку, сачекајте…</string> + <string name="pgp_message">ОпенПГП шифрована порука</string> <string name="nick_in_use">Надимак је већ у употреби</string> <string name="admin">Администратор</string> <string name="owner">Власник</string> @@ -44,7 +45,7 @@ <string name="remove_bookmark_text">Желите ли да уклоните %s са обележивача? Преписка са овим контактом неће бити уклоњена.</string> <string name="register_account">Региструј нови налог на серверу</string> <string name="change_password_on_server">Промени лозинку на серверу</string> - <string name="share_with">Подели са…</string> + <string name="share_with">Подели помоћу…</string> <string name="start_conversation">Почни преписку</string> <string name="invite_contact">Позови контакта</string> <string name="contacts">Контакти</string> @@ -63,7 +64,7 @@ <string name="send_never">Не питај више</string> <string name="problem_connecting_to_account">Не могу да се повежем са налогом</string> <string name="problem_connecting_to_accounts">Не могу да се повежем са више налога</string> - <string name="touch_to_fix">Додирните овде да бисте управљали вашим налозима</string> + <string name="touch_to_fix">Тапните овде да бисте управљали вашим налозима</string> <string name="attach_file">Приложи фајл</string> <string name="not_in_roster">Контакт није на вашем списку контаката. Желите ли да га додате?</string> <string name="add_contact">Додај контакт</string> @@ -76,8 +77,10 @@ <string name="delete_messages">Обриши поруке</string> <string name="also_end_conversation">Окончај ову преписку након тога</string> <string name="choose_presence">Избор присутности за контакта</string> - <string name="send_plain_text_message">Пошаљи обичну текстуалну поруку</string> + <string name="send_unencrypted_message">Пошаљи нешифровану поруку</string> <string name="send_otr_message">Пошаљи ОТР шифровану поруку</string> + <string name="send_omemo_message">Пошаљи ОМЕМО шифровану поруку</string> + <string name="send_omemo_x509_message">Пошаљи v\\ОМЕМО шифровану поруку</string> <string name="send_pgp_message">Пошаљи ОпенПГП шифровану поруку</string> <string name="your_nick_has_been_changed">Ваш надимак је промењен</string> <string name="send_unencrypted">Пошаљи нешифровано</string> @@ -86,13 +89,14 @@ <string name="openkeychain_required_long">Конверзација користи апликацију <b>Отворени кључарник</b> за шифровање и дешифровање порука и управљање вашим јавним кључевима.\n\nОтворени кључарник је лиценциран под ГПЛв3 и доступан је на Ф-дроиду у Гугловој Плеј продавници.\n\n<small>(Поново покрените Конверзацију након тога.)</small></string> <string name="restart">Поново покрени</string> <string name="install">Инсталирај</string> + <string name="openkeychain_not_installed">Инсталирајте Отворени кључарник</string> <string name="offering">нудим…</string> <string name="waiting">чекам…</string> <string name="no_pgp_key">Нема ОпенПГП кључа</string> <string name="contact_has_no_pgp_key">Конверзација није могла да шифрује ваше поруке јер ваш контакт не објављује свој јавни кључ.\n\n<small>Замолите вашег контакта да постави ОпенПГП.</small></string> <string name="no_pgp_keys">Нема ОпенПГП кључева</string> <string name="contacts_have_no_pgp_keys">Конверзација није могла да шифрује ваше поруке јер ваши контакти не објављују свој јавни кључ.\n\n<small>Замолите ваше контакте да поставе ОпенПГП.</small></string> - <string name="encrypted_message_received"><i>Примљена је шифрована порука. Додирните за дешифровање и приказ.</i></string> + <string name="encrypted_message_received"><i>Примљена је шифрована порука. Тапните за дешифровање.</i></string> <string name="pref_general">Опште</string> <string name="pref_xmpp_resource">ИксМПП ресурс</string> <string name="pref_xmpp_resource_summary">Име са којим се овај клијент идентификује</string> @@ -105,10 +109,8 @@ <string name="pref_vibrate_summary">Вибрирај кад стигне нова порука</string> <string name="pref_sound">Звук</string> <string name="pref_sound_summary">Звуци обавештења</string> - <string name="pref_conference_notifications">Обавештења конференције</string> - <string name="pref_conference_notifications_summary">Увек обавести кад нова порука у конференцији стигне уместо само кад је означена</string> <string name="pref_notification_grace_period">Период одгоде обавештења</string> - <string name="pref_notification_grace_period_summary">Онемогући обавештења на кратко по примању карбон копије</string> + <string name="pref_notification_grace_period_summary">Искључи обавештења на кратко по примању карбон копије</string> <string name="pref_advanced_options">Напредне поставке</string> <string name="pref_never_send_crash">Никад не шаљи извештаје о паду</string> <string name="pref_never_send_crash_summary">Слањем контратрага помажете текући развој Конверзације</string> @@ -136,7 +138,7 @@ <string name="error_io_exception">Општа У/И грешка. Можда вам је нестало простора у складишту?</string> <string name="error_security_exception_during_image_copy">Апликација којом сте изабрали ову слику није дала довољне дозволе за читање фајла.\n\n<small>Користите други менаџер фајлова да изаберете слику</small></string> <string name="account_status_unknown">Непознато</string> - <string name="account_status_disabled">Привремено онемогућен</string> + <string name="account_status_disabled">Привремено искључен</string> <string name="account_status_online">На вези</string> <string name="account_status_connecting">Повезивање\u2026</string> <string name="account_status_offline">Ван везе</string> @@ -149,15 +151,16 @@ <string name="account_status_regis_not_sup">Сервер не подржава регистрацију</string> <string name="account_status_security_error">Безбедносна грешка</string> <string name="account_status_incompatible_server">Некомпатибилан сервер</string> - <string name="encryption_choice_none">Обичан текст</string> + <string name="encryption_choice_unencrypted">Нешифровано</string> <string name="encryption_choice_otr">ОТР</string> <string name="encryption_choice_pgp">ОпенПГП</string> + <string name="encryption_choice_omemo">ОМЕМО</string> <string name="mgmt_account_edit">Уреди налог</string> <string name="mgmt_account_delete">Обриши налог</string> - <string name="mgmt_account_disable">Привремено онемогући</string> + <string name="mgmt_account_disable">Привремено искључи</string> <string name="mgmt_account_publish_avatar">Објави аватар</string> <string name="mgmt_account_publish_pgp">Објави ОпенПГП јавни кључ</string> - <string name="mgmt_account_enable">Омогући налог</string> + <string name="mgmt_account_enable">Укључи налог</string> <string name="mgmt_account_are_you_sure">Да ли сте сигурни?</string> <string name="mgmt_account_delete_confirm_text">Ако обришете ваш налог изгубићете сав историјат преписке</string> <string name="attach_record_voice">Сними глас</string> @@ -170,14 +173,14 @@ <string name="passwords_do_not_match">Лозинке се не поклапају</string> <string name="invalid_jid">Ово није исправан Џабер ИД</string> <string name="error_out_of_memory">Нестало меморије. Слика је превелика</string> - <string name="add_phone_book_text">Желите ли да додате %s у именик вашег телефона?</string> + <string name="add_phone_book_text">Желите ли да додате %s у ваш именик?</string> <string name="contact_status_online">на вези</string> <string name="contact_status_free_to_chat">слободан за ћаскање</string> <string name="contact_status_away">одсутан</string> <string name="contact_status_extended_away">продужено одсутан</string> <string name="contact_status_do_not_disturb">не узнемиравај</string> <string name="contact_status_offline">ван везе</string> - <string name="muc_details_conference">Конференција</string> + <string name="muc_details_conference">Групно ћаскање</string> <string name="muc_details_other_members">Остали чланови</string> <string name="server_info_show_more">Подаци о серверу</string> <string name="server_info_mam">XEP-0313: МАМ</string> @@ -186,7 +189,8 @@ <string name="server_info_blocking">XEP-0191: наредба блокирања</string> <string name="server_info_roster_version">XEP-0237: верзионисање ростера</string> <string name="server_info_stream_management">XEP-0198: менаџмент тока</string> - <string name="server_info_pep">XEP-0163: ПЕП (аватари)</string> + <string name="server_info_pep">XEP-0163: PEP (аватари/ОМЕМО)</string> + <string name="server_info_http_upload">XEP-0363: ХТТП отпремање фајлова</string> <string name="server_info_available">доступан</string> <string name="server_info_unavailable">недоступан</string> <string name="missing_public_keys">Недостају објаве јавног кључа</string> @@ -204,33 +208,45 @@ <string name="reception_failed">Примање није успело</string> <string name="your_fingerprint">Ваш отисак</string> <string name="otr_fingerprint">ОТР отисак</string> + <string name="omemo_fingerprint">ОМЕМО отисак</string> + <string name="omemo_fingerprint_x509">v\\ОМЕМО отисак</string> + <string name="omemo_fingerprint_selected_message">ОМЕМО отисак поруке</string> + <string name="omemo_fingerprint_x509_selected_message">v\\ОМЕМО отисак поруке</string> + <string name="this_device_omemo_fingerprint">Сопствени ОМЕМО отисак</string> + <string name="other_devices">Остали уређаји</string> + <string name="trust_omemo_fingerprints">Поуздај се у ОМЕМО отиске</string> + <string name="fetching_keys">Добављам кључеве…</string> + <string name="done">Готово</string> <string name="verify">Овери</string> <string name="decrypt">Дешифруј</string> - <string name="conferences">Конференције</string> + <string name="conferences">Групна ћаскања</string> <string name="search">Тражи</string> <string name="create_contact">Направи контакт</string> - <string name="join_conference">Придружи се конференцији</string> + <string name="enter_contact">Унеси контакт</string> + <string name="join_conference">Придружи се групном ћаскању</string> <string name="delete_contact">Обриши контакт</string> <string name="view_contact_details">Прикажи детаље контакта</string> <string name="block_contact">Блокирај контакт</string> <string name="unblock_contact">Одблокирај контакт</string> <string name="create">Направи</string> + <string name="select">Изабери</string> <string name="contact_already_exists">Контакт већ постоји</string> <string name="join">Придружи се</string> - <string name="conference_address">Адреса конференције</string> + <string name="conference_address">Адреса групног ћаскања</string> <string name="conference_address_example">soba@konferencija.primer.com</string> <string name="save_as_bookmark">Сачувај као обележивач</string> <string name="delete_bookmark">Обриши обележивач</string> <string name="bookmark_already_exists">Овај обележивач већ постоји</string> <string name="you">Ви</string> - <string name="action_edit_subject">Уреди предмет конференције</string> - <string name="conference_not_found">Конференција није нађена</string> + <string name="action_edit_subject">Уреди предмет групног ћаскања</string> + <string name="conference_not_found">Групно ћаскање није нађено</string> + <string name="conference_unknown_error">Примљена је непозната грешка</string> <string name="leave">Напусти</string> <string name="contact_added_you">Контакт вас је додао на списак контаката</string> <string name="add_back">Додај га</string> <string name="contact_has_read_up_to_this_point">%s је прочитао довде</string> <string name="publish">Објави</string> - <string name="touch_to_choose_picture">Додирните аватар да изаберете слику из галерије</string> + <string name="touch_to_choose_picture">Тапните аватар да изаберете слику из галерије</string> <string name="publish_avatar_explanation">Имајте на уму: Свима који су претплаћени на ваше ажурирање присутности биће дозвољено да виде ову слику.</string> <string name="publishing">Објављујем…</string> <string name="error_publish_avatar_server_reject">Сервер је одбио вашу објаву</string> @@ -247,11 +263,10 @@ <string name="server_info_session_established">Текућа сесија успостављена</string> <string name="additional_information">Додатни подаци</string> <string name="skip">Прескочи</string> - <string name="disable_notifications">Онемогући обавештења</string> - <string name="disable_notifications_for_this_conversation">Онемогући обавештења за ову преписку</string> - <string name="notifications_disabled">Обавештења су онемогућена</string> - <string name="enable">Омогући</string> - <string name="conference_requires_password">Конференција захтева лозинку</string> + <string name="disable_notifications">Искључи обавештења</string> + <string name="disable_notifications_for_this_conversation">Искључи обавештења за ову преписку</string> + <string name="enable">Укључи</string> + <string name="conference_requires_password">Групно ћаскање захтева лозинку</string> <string name="enter_password">Унесите лозинку</string> <string name="missing_presence_updates">Нема ажуриране присутности од контакта</string> <string name="request_presence_updates">Најпре захтевајте ажурирање присутности од вашег контакта.\n\n<small>Ово ће омогућити да се одреди којег клијента ваш контакт користи.</small></string> @@ -262,7 +277,7 @@ <string name="without_mutual_presence_updates"><b>Упозорење:</b> Слање овога без узајамних ажурирања присутности би могло да узрокује неочекиване проблеме.\n\n<small>Идите на детаље контакта да бисте проверили претплате на присутности.</small></string> <string name="pref_encryption_settings">Поставке шифровања</string> <string name="pref_force_encryption">Присили крај-на-крај шифровање</string> - <string name="pref_force_encryption_summary">Увек шифруј поруке (осим за конференције)</string> + <string name="pref_force_encryption_summary">Увек шифруј поруке (осим за групна ћаскања)</string> <string name="pref_dont_save_encrypted">Не успремај шифроване поруке</string> <string name="pref_dont_save_encrypted_summary">Упозорење: Ово може да доведе до губитка порука</string> <string name="pref_expert_options">Опције за стручњаке</string> @@ -272,8 +287,8 @@ <string name="title_pref_quiet_hours">Тихи сати</string> <string name="title_pref_quiet_hours_start_time">Време почетка</string> <string name="title_pref_quiet_hours_end_time">Време завршетка</string> - <string name="title_pref_enable_quiet_hours">Омогући тихе сате</string> - <string name="pref_quiet_hours_summary">Обавештења ће бити утишана за време тихих сати</string> + <string name="title_pref_enable_quiet_hours">Укључи тихе сате</string> + <string name="pref_quiet_hours_summary">Обавештења ће бити ућуткана за време тихих сати</string> <string name="pref_use_larger_font">Повећај величину фонта</string> <string name="pref_use_larger_font_summary">Користи веће величине фонта за читаву апликацију</string> <string name="pref_use_send_button_to_indicate_status">Дугме слања показује стање</string> @@ -281,12 +296,13 @@ <string name="pref_use_indicate_received_summary">Означи примљене поруке зеленом квачицом, ако је подржано</string> <string name="pref_use_send_button_to_indicate_status_summary">Боја дугмета за слање показује стање контакта</string> <string name="pref_expert_options_other">Остало</string> - <string name="pref_conference_name">Име конференције</string> - <string name="pref_conference_name_summary">Предмет собе уместо ЈИД-а идентификује конференцију</string> + <string name="pref_conference_name">Назив групног ћаскања</string> + <string name="pref_conference_name_summary">Предмет собе уместо ЈИД-а идентификује групно ћаскање</string> <string name="toast_message_otr_fingerprint">ОТР отисак копиран на клипборд!</string> - <string name="conference_banned">Забрањени сте на овој конференцији</string> - <string name="conference_members_only">Ова конференција је само за чланове</string> - <string name="conference_kicked">Шутнути сте из ове конференције</string> + <string name="toast_message_omemo_fingerprint">ОМЕМО отисак копиран на клипборд!</string> + <string name="conference_banned">Забрањени сте на овом групном ћаскању</string> + <string name="conference_members_only">Ово групно ћаскање је само за чланове</string> + <string name="conference_kicked">Шутнути сте из овог групног ћаскања</string> <string name="using_account">преко налога %s</string> <string name="checking_x">Проверавам %s на ХТТП домаћину</string> <string name="not_connected_try_again">Нисте повезани. Покушајте поново касније</string> @@ -307,7 +323,6 @@ <string name="verify_otr">Овери ОТР</string> <string name="remote_fingerprint">Отисак удаљеног</string> <string name="scan">очитај</string> - <string name="or_touch_phones">(или додирните телефоне)</string> <string name="smp">Социјалистички милионер протокол</string> <string name="shared_secret_hint">Наговештај или питање</string> <string name="shared_secret_secret">Заједничка тајна</string> @@ -324,6 +339,9 @@ <string name="conversations_foreground_service">Конверзација</string> <string name="pref_keep_foreground_service">Држи сервис у првом плану</string> <string name="pref_keep_foreground_service_summary">Спречава оперативни систем да прекине вашу везу</string> + <string name="pref_export_logs">Извези записе</string> + <string name="pref_export_logs_summary">Упис записа на СД картицу</string> + <string name="notification_export_logs_title">Уписујем записе на СД картицу</string> <string name="choose_file">Изабери фајл</string> <string name="receiving_x_file">Примам %1$s (%2$d%% завршено)</string> <string name="download_x_file">Преузми %s</string> @@ -341,15 +359,26 @@ <string name="are_you_sure_verify_fingerprint">Желите ли заиста да оверите ОТР отисак вашег контакта?</string> <string name="pref_show_dynamic_tags">Прикажи динамичке ознаке</string> <string name="pref_show_dynamic_tags_summary">Приказ ознака испод контаката</string> - <string name="enable_notifications">Омогући обавештења</string> - <string name="conference_with">Направи конференцију са…</string> - <string name="no_conference_server_found">Сервер конференције није нађен</string> - <string name="conference_creation_failed">Прављење конференције није успело!</string> - <string name="conference_created">Конференција направљена!</string> + <string name="enable_notifications">Укључи обавештења</string> + <string name="conference_with">Направи групно ћаскање са…</string> + <string name="no_conference_server_found">Сервер групног ћаскања није нађен</string> + <string name="conference_creation_failed">Прављење групног ћаскања није успело!</string> + <string name="conference_created">Групно ћаскање направљено!</string> <string name="secret_accepted">Тајна прихваћена!</string> <string name="reset">Ресетуј</string> <string name="account_image_description">Аватар налога</string> <string name="copy_otr_clipboard_description">Копирај ОТР отисак на клипборд</string> + <string name="copy_omemo_clipboard_description">Копирај ОМЕМО отисак на клипборд</string> + <string name="regenerate_omemo_key">Поново генериши ОМЕМО кључ</string> + <string name="wipe_omemo_pep">Уклони остале уређаје са ПЕП-а</string> + <string name="clear_other_devices">Очисти уређаје</string> + <string name="clear_other_devices_desc">Желите ли заиста да уклоните све остале уређаје са ОМЕМО објаве? Када се ваши уређаји следећи пут повежу, објавиће се сами, али у међувремену можда неће примати поруке.</string> + <string name="purge_key">Очисти кључ</string> + <string name="purge_key_desc_part1">Желите ли заиста да очистите овај кључ?</string> + <string name="purge_key_desc_part2">Неповратно ће бити сматран компровитованим, и њиме више никад нећете моћи да успоставите сесију.</string> + <string name="error_no_keys_to_trust_server_error">Нема употребљивих кључева за овај контакт.\nДобављање нових кључева са сервера није било успешно. Можда нешто није у реду са сервером ваших контаката.</string> + <string name="error_no_keys_to_trust">Нема употребљивих кључева за овај контакт. Ако сте очистили било који од тих кључева, контакт мора да генерише нови.</string> + <string name="error_trustkeys_title">Грешка</string> <string name="fetching_history_from_server">Добављам историјат са сервера</string> <string name="no_more_history_on_server">Нема више историјата на серверу</string> <string name="updating">Ажурирам…</string> @@ -366,8 +395,8 @@ <string name="current_password">Текућа лозинка</string> <string name="new_password">Нова лозинка</string> <string name="password_should_not_be_empty">Лозинка не може бити празна</string> - <string name="enable_all_accounts">Омогући све налоге</string> - <string name="disable_all_accounts">Онемогући све налоге</string> + <string name="enable_all_accounts">Укључи све налоге</string> + <string name="disable_all_accounts">Искључи све налоге</string> <string name="perform_action_with">Изврши радњу са</string> <string name="no_affiliation">Без припадности</string> <string name="no_role">Без улоге</string> @@ -378,19 +407,21 @@ <string name="remove_membership">Опозови чланство</string> <string name="grant_admin_privileges">Одобри админ. привилегије</string> <string name="remove_admin_privileges">Одобри админ. привилегије</string> - <string name="remove_from_room">Уклони из конференције</string> + <string name="remove_from_room">Уклони из групног ћаскања</string> <string name="could_not_change_affiliation">Не могу да изменим припадност за %s</string> - <string name="ban_from_conference">Забрани са конференције</string> - <string name="removing_from_public_conference">Покушавате да уклоните %s са јавне конференције. Једини начин да то урадите је да забраните тог корисника заувек.</string> + <string name="ban_from_conference">Забрани на групном ћаскању</string> + <string name="removing_from_public_conference">Покушавате да уклоните %s са групног ћаскања. Једини начин да то урадите је да забраните тог корисника заувек.</string> <string name="ban_now">Забрани одмах</string> <string name="could_not_change_role">Не могу да изменим улогу за %s</string> - <string name="public_conference">Јавно доступна конференција</string> - <string name="private_conference">Лична, само за чланове конференција</string> - <string name="conference_options">Опције конференције</string> - <string name="members_only">Лична (само чланови)</string> + <string name="public_conference">Јавно доступно групно ћаскање</string> + <string name="private_conference">Лично, само за чланове</string> + <string name="conference_options">Опције групног ћаскања</string> + <string name="members_only">Лична, само чланови</string> <string name="non_anonymous">Неанонимна</string> - <string name="modified_conference_options">Поставке конференције измењене!</string> - <string name="could_not_modify_conference_options">Не могу да изменим поставке конференције</string> + <string name="moderated">Уређивана</string> + <string name="you_are_not_participating">Не учествујете</string> + <string name="modified_conference_options">Поставке групног ћаскања измењене!</string> + <string name="could_not_modify_conference_options">Не могу да изменим поставке групног ћаскања</string> <string name="never">никад</string> <string name="thirty_minutes">30 минута</string> <string name="one_hour">1 сат</string> @@ -409,14 +440,14 @@ <string name="apk">Апликација за Андроид</string> <string name="vcard">Контакт</string> <string name="received_x_file">Примљено %s</string> - <string name="disable_foreground_service">Онемогући сервис у првом плану</string> - <string name="touch_to_open_conversations">Додирните да отворите Конверзацију</string> + <string name="disable_foreground_service">Искључи сервис у првом плану</string> + <string name="touch_to_open_conversations">Тапните да отворите Конверзацију</string> <string name="avatar_has_been_published">Аватар је објављен!</string> <string name="sending_x_file">Шаљем %s</string> <string name="offering_x_file">Нудим %s</string> <string name="hide_offline">Сакриј неповезане</string> - <string name="disable_account">Онемогући налог</string> - <string name="contact_is_typing">%s куца...</string> + <string name="disable_account">Искључи налог</string> + <string name="contact_is_typing">%s куца…</string> <string name="contact_has_stopped_typing">%s престаде да куца</string> <string name="pref_chat_states">Обавештења о куцању</string> <string name="pref_chat_states_summary">Обзнаните контакту кад куцате нову поруку</string> @@ -426,9 +457,8 @@ <string name="location">Локација</string> <string name="received_location">Примљена локација</string> <string name="title_undo_swipe_out_conversation">Преписка затворена</string> - <string name="title_undo_swipe_out_muc">Напусти конференцију</string> - <string name="pref_certificate_options">Опције сертификата</string> - <string name="pref_dont_trust_system_cas_title">Не веруј системским сертификационим телима</string> + <string name="title_undo_swipe_out_muc">Напусти групно ћаскање</string> + <string name="pref_dont_trust_system_cas_title">Не поуздај се у системска сертификациона тела</string> <string name="pref_dont_trust_system_cas_summary">Сви сертификати морају ручно да се одобре</string> <string name="pref_remove_trusted_certificates_title">Уклони сертификате</string> <string name="pref_remove_trusted_certificates_summary">Обриши ручно одобрене сертификате</string> @@ -451,6 +481,70 @@ <string name="none">Ниједна</string> <string name="recently_used">Недавно коришћена</string> <string name="choose_quick_action">Изаберите брзу радњу</string> - <string name="file_not_found_on_remote_host">Фајл није нађен на удаљеном серверу</string> <string name="search_for_contacts_or_groups">Тражите контакте или групе</string> + <string name="send_private_message">Пошаљи личну поруку</string> + <string name="user_has_left_conference">%s напусти групно ћаскање!</string> + <string name="username">Корисничко име</string> + <string name="username_hint">Корисничко име</string> + <string name="invalid_username">Ово није исправно корисничко име</string> + <string name="download_failed_server_not_found">Преузимање није успело: сервер није нађен</string> + <string name="download_failed_file_not_found">Преузимање није успело: фајл није нађен</string> + <string name="download_failed_could_not_connect">Преузимање није успело: не могу да се повежем са домаћином</string> + <string name="account_status_tor_unavailable">Тор мрежа недоступна</string> + <string name="server_info_broken">Оштећен</string> + <string name="pref_presence_settings">Поставке присутности</string> + <string name="pref_away_when_screen_off">Одсутан кад је екран искључен</string> + <string name="pref_away_when_screen_off_summary">Означава ваш ресурс одсутним кад је екран искључен</string> + <string name="pref_xa_on_silent_mode">Недоступан у тихом режиму</string> + <string name="pref_xa_on_silent_mode_summary">Означава ваш ресурс недоступним кад је уређај у тихом режиму</string> + <string name="hostname_example">xmpp.primer.com</string> + <string name="action_add_account_with_certificate">Додај налог сертификатом</string> + <string name="unable_to_parse_certificate">Не могу да рашчланим сертификат</string> + <string name="authenticate_with_certificate">Оставите празно за аутентификацију сертификатом</string> + <string name="captcha_ocr">Текст стопке</string> + <string name="captcha_required">Потребна стопка</string> + <string name="captcha_hint">унесите текст са слике</string> + <string name="certificate_chain_is_not_trusted">Ланац сертификата није поуздан</string> + <string name="jid_does_not_match_certificate">Џабер ИД не одговара сертификату</string> + <string name="action_renew_certificate">Обнови сертификат</string> + <string name="error_fetching_omemo_key">Грешка добављања ОМЕМО кључа!</string> + <string name="verified_omemo_key_with_certificate">Оверен ОМЕМО кључ помоћу сертификата!</string> + <string name="device_does_not_support_certificates">Ваш уређај не подржава избор сертификата клијента!</string> + <string name="pref_connection_options">Опције повезивања</string> + <string name="account_settings_hostname">Име домаћина</string> + <string name="account_settings_port">Порт</string> + <string name="not_a_valid_port">Ово није исправан број порта</string> + <string name="not_valid_hostname">Ово није исправно име домаћина</string> + <string name="connected_accounts">%1$d од %2$d налога повезано</string> + <plurals name="x_messages"> + <item quantity="one">%d порука</item> + <item quantity="few">%d поруке</item> + <item quantity="other">%d порука</item> + </plurals> + <string name="shared_file_with_x">Подељен фајл са %s</string> + <string name="shared_image_with_x">Подељена слика са %s</string> + <string name="no_storage_permission">Конверзацији је потребан приступ спољашњем складишту</string> + <string name="sync_with_contacts">Синхронизуј са контактима</string> + <string name="sync_with_contacts_long">Конверзација жели да поклапи ваш ИксМПП именик са контактима на вашем уређају да би приказала њихова пуна имена и аватаре.\n\nКонверзација ће само да очита ваше контакте и упореди их локално без отпремања на сервер.\n\nСада ћете бити упитани за дозволу приступа вашим контактима.</string> + <string name="certificate_information">Подаци о сертификату</string> + <string name="certificate_subject">Предмет</string> + <string name="certificate_issuer">Издавач</string> + <string name="certificate_cn">Заједничко име</string> + <string name="certificate_o">Организација</string> + <string name="certificate_sha1">СХА1</string> + <string name="certicate_info_not_available">(није доступно)</string> + <string name="certificate_not_found">Сертификат није нађен</string> + <string name="notify_on_all_messages">Обавештења за све поруке</string> + <string name="notify_only_when_highlighted">Обавештења само за означене поруке</string> + <string name="notify_never">Обавештења искључена</string> + <string name="notify_paused">Обавештења паузирана</string> + <string name="always">увек</string> + <string name="automatically">аутоматски</string> + <string name="battery_optimizations_enabled">Оптимизација батерије је укључена</string> + <string name="battery_optimizations_enabled_explained">Ваш уређај користи оптимизацију потрошње батерије за Конверзацију што може да доведе до застоја обавештења или чак губитка порука.\nПрепоручљиво је да то искључите.</string> + <string name="battery_optimizations_enabled_dialog">Ваш уређај користи оптимизацију потрошње батерије за Конверзацију што може да доведе до застоја обавештења или чак губитка порука.\n\nСада ћете бити упитани да то искључите.</string> + <string name="disable">Искључи</string> + <string name="selection_too_large">Назначена површина је превелика</string> + <string name="no_accounts">(Нема активираних налога)</string> + <string name="this_field_is_required">Ово поље је захтевано</string> </resources> diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index d635831c..00db3f2e 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">Säker konversation</string> <string name="action_add_account">Lägg till konto</string> <string name="action_edit_contact">Ändra namn</string> - <string name="action_add_phone_book">Lägg till i telefonbok</string> + <string name="action_add_phone_book">Lägg till i kontakter</string> <string name="action_delete_contact">Ta bort kontakt</string> <string name="action_block_contact">Blockera kontakt</string> <string name="action_unblock_contact">Avblockera kontakt</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d min sedan</string> <string name="unread_conversations">olästa konversationer</string> <string name="sending">skickar…</string> - <string name="encrypted_message">Avkrypterar meddelande. Vänta…</string> + <string name="message_decrypting">Avkrypterar meddelande. Vänta…</string> + <string name="pgp_message">OpenPGP-krypterat meddelande</string> <string name="nick_in_use">Nick används redan</string> <string name="admin">Admin</string> <string name="owner">Ägare</string> @@ -58,7 +59,7 @@ <string name="save">Spara</string> <string name="ok">Ok</string> <string name="crash_report_title">Conversations har kraschat</string> - <string name="crash_report_message">Genom att skicka in stack traces hjälper du utvecklarna av Conversations\n<b>Varning:</b> Detta använder ditt XMPP konto för att skicka informationen till utvecklarna.</string> + <string name="crash_report_message">Genom att skicka in stack traces hjälper du utvecklarna av Conversations\n<b>Varning:</b> Detta använder ditt XMPP-konto för att skicka informationen till utvecklarna.</string> <string name="send_now">Skicka nu</string> <string name="send_never">Fråga aldrig igen</string> <string name="problem_connecting_to_account">Kan inte ansluta till konto</string> @@ -74,10 +75,12 @@ <string name="clear_conversation_history">Rensa konversationshistorik</string> <string name="clear_histor_msg">Vill du ta bort alla meddelanden i denna konversation?\n\n<b>Varning:</b> Detta kommer inte påverka meddelanden lagrade på andra enheter eller servrar.</string> <string name="delete_messages">Ta bort meddelanden</string> - <string name="also_end_conversation">Avsluta sedan denna konversation</string> + <string name="also_end_conversation">Avsluta denna konversation efteråt</string> <string name="choose_presence">Välj tillgänglighet till kontakt</string> - <string name="send_plain_text_message">Skicka meddelande i klartext</string> + <string name="send_unencrypted_message">Skicka okrypterat meddelande</string> <string name="send_otr_message">Skicka OTR-krypterat meddelande</string> + <string name="send_omemo_message">Skicka OMEMO-krypterat meddelande</string> + <string name="send_omemo_x509_message">Skicka v\\OMEMO-krypterat meddelande</string> <string name="send_pgp_message">Skicka OpenPGP-krypterat meddelande</string> <string name="your_nick_has_been_changed">Ditt nick har ändrats</string> <string name="send_unencrypted">Skicka okrypterat</string> @@ -86,35 +89,34 @@ <string name="openkeychain_required_long">Conversations använder en tredjeparts-applikation som heter <b>OpenKeychain</b> för att kryptera och avkryptera meddelanden och hantera dina publika nycklar.\n\nOpenKeychain är licensierad under GPLv3 och tillgänglig på F-Droid och Google Play.\n\n<small>(Starta om Conversations efter installation.)</small></string> <string name="restart">Starta om</string> <string name="install">Installera</string> + <string name="openkeychain_not_installed">Installera OpenKeychain</string> <string name="offering">erbjuder…</string> <string name="waiting">väntar…</string> <string name="no_pgp_key">Ingen OpenPGP-nyckel funnen</string> - <string name="contact_has_no_pgp_key">Conversations kan inte avkryptera ditt meddelande eftersom din kontakt inte annonserar sin publika nyckel.\n\n<small>Be din kontakt att sätta upp OpenPGP.</small></string> + <string name="contact_has_no_pgp_key">Conversations kan inte kryptera ditt meddelande eftersom din kontakt inte annonserar sin publika nyckel.\n\n<small>Be din kontakt att sätta upp OpenPGP.</small></string> <string name="no_pgp_keys">Inga OpenPGP-nycklar funna</string> - <string name="contacts_have_no_pgp_keys">Conversations kan inte avkryptera ditt meddelande eftersom din kontakt inte annonserar sin publika nyckel.\n\n<small>Be din kontakt att sätta upp OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Krypterat meddelande mottaget. Tryck för att se och avkryptera.</i></string> + <string name="contacts_have_no_pgp_keys">Conversations kan inte kryptera ditt meddelande eftersom din kontakt inte annonserar sin publika nyckel.\n\n<small>Be din kontakt att sätta upp OpenPGP.</small></string> + <string name="encrypted_message_received"><i>Krypterat meddelande mottaget. Tryck för att avkryptera.</i></string> <string name="pref_general">Generellt</string> - <string name="pref_xmpp_resource">XMPP resurs</string> - <string name="pref_xmpp_resource_summary">Namnet som klienten identifierar sig med</string> + <string name="pref_xmpp_resource">XMPP-resurs</string> + <string name="pref_xmpp_resource_summary">Namnet klienten identifierar sig med</string> <string name="pref_accept_files">Acceptera filer</string> - <string name="pref_accept_files_size_summary">Acceptera automatiskt filer som är mindre än…</string> - <string name="pref_notification_settings">Notifieringsinställningar</string> + <string name="pref_accept_files_summary">Acceptera automatiskt filer som är mindre än…</string> + <string name="pref_notification_settings">Notifiering</string> <string name="pref_notifications">Notifieringar</string> <string name="pref_notifications_summary">Notifiera när meddelande tagits emot</string> <string name="pref_vibrate">Vibrera</string> <string name="pref_vibrate_summary">Vibrera när meddelande tagits emot</string> <string name="pref_sound">Ljud</string> <string name="pref_sound_summary">Spela ljud med notifiering</string> - <string name="pref_conference_notifications">Konferensnotifieringar</string> - <string name="pref_conference_notifications_summary">Notifiera alltid när nytt konferensmeddelande tagits emot i stället för endast vid highlight</string> <string name="pref_notification_grace_period">Notifieringsfrist</string> <string name="pref_notification_grace_period_summary">Inaktivera notifieringar en kort stund efter att en carbon copy tagits emot</string> - <string name="pref_advanced_options">Avancerade inställningar</string> + <string name="pref_advanced_options">Avancerat</string> <string name="pref_never_send_crash">Skicka aldrig krasch-rapporter</string> <string name="pref_never_send_crash_summary">Genom att skicka in stack traces hjälper du utvecklarna av Conversations</string> <string name="pref_confirm_messages">Bekräfta meddelanden</string> <string name="pref_confirm_messages_summary">Låter dina kontakter veta när du har tagit emot och läst ett meddelande</string> - <string name="pref_ui_options">UI inställningar</string> + <string name="pref_ui_options">Gränssnitt</string> <string name="openpgp_error">OpenKeychain rapporterade ett fel</string> <string name="error_decrypting_file">I/O-fel vid avkryptering av fil</string> <string name="accept">Acceptera</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">Servern stödjer inte registrering</string> <string name="account_status_security_error">Säkerhetsfel</string> <string name="account_status_incompatible_server">Inkompatibel server</string> - <string name="encryption_choice_none">Klartext</string> + <string name="encryption_choice_unencrypted">Okrypterat</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">Ändra konto</string> <string name="mgmt_account_delete">Ta bort</string> <string name="mgmt_account_disable">Inaktivera tillfälligt</string> @@ -161,16 +164,16 @@ <string name="mgmt_account_are_you_sure">Är du säker?</string> <string name="mgmt_account_delete_confirm_text">Om du tar bort kontot kommer all konversationshistorik att försvinna</string> <string name="attach_record_voice">Spela in röst</string> - <string name="account_settings_jabber_id">Jabber ID</string> + <string name="account_settings_jabber_id">Jabber-ID</string> <string name="account_settings_password">Lösenord</string> <string name="account_settings_example_jabber_id">användarnamn@exempel.se</string> <string name="account_settings_confirm_password">Bekräfta lösenord</string> <string name="password">Lösenord</string> <string name="confirm_password">Bekräfta lösenord</string> <string name="passwords_do_not_match">Lösenorden är inte lika</string> - <string name="invalid_jid">Detta är inte ett korrekt Jabber ID</string> + <string name="invalid_jid">Detta är inte ett korrekt Jabber-ID</string> <string name="error_out_of_memory">Slut på minne. Bilden är för stor</string> - <string name="add_phone_book_text">Vill du lägga till %s i din telefons kontaktlista?</string> + <string name="add_phone_book_text">Vill du lägga till %s i din enhets kontakter?</string> <string name="contact_status_online">online</string> <string name="contact_status_free_to_chat">tillgänglig</string> <string name="contact_status_away">borta</string> @@ -179,14 +182,16 @@ <string name="contact_status_offline">offline</string> <string name="muc_details_conference">Konferens</string> <string name="muc_details_other_members">Andra medlemmar</string> - <string name="server_info_show_more">Server info</string> + <string name="server_info_show_more">Server-info</string> <string name="server_info_mam">XEP-0313: Message Archive</string> - <string name="server_info_carbon_messages">Carbon Messages</string> + <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> <string name="server_info_csi">XEP-0352: Client State Indication</string> <string name="server_info_blocking">XEP-0191: Blocking Command</string> <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> - <string name="server_info_stream_management">Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatarbilder)</string> + <string name="server_info_stream_management">XEP-0198: Stream Management</string> + <string name="server_info_pep">XEP-0163: PEP (Avatarbilder / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: Ladda upp via HTTP</string> + <string name="server_info_push">XEP-0357: Push</string> <string name="server_info_available">tillgänglig</string> <string name="server_info_unavailable">otillgänglig</string> <string name="missing_public_keys">Annonsering om publik nyckel saknas</string> @@ -204,17 +209,28 @@ <string name="reception_failed">Mottagning misslyckades</string> <string name="your_fingerprint">Ditt fingeravtryck</string> <string name="otr_fingerprint">OTR-fingeravtryck</string> + <string name="omemo_fingerprint">OMEMO-fingeravtryck</string> + <string name="omemo_fingerprint_x509">v\\OMEMO-fingeravtryck</string> + <string name="omemo_fingerprint_selected_message">Meddelandets OMEMO-fingeravtryck</string> + <string name="omemo_fingerprint_x509_selected_message">Meddelandets v\\OMEMO-fingeravtryck</string> + <string name="this_device_omemo_fingerprint">Eget OMEMO-fingeravtryck</string> + <string name="other_devices">Andra enheter</string> + <string name="trust_omemo_fingerprints">Lita på OMEMO-fingeravtryck</string> + <string name="fetching_keys">Hämtar nycklar...</string> + <string name="done">Klar</string> <string name="verify">Verifiera</string> <string name="decrypt">Avkryptera</string> <string name="conferences">Konferenser</string> <string name="search">Sök</string> <string name="create_contact">Skapa kontakt</string> + <string name="enter_contact">Fyll i kontakt</string> <string name="join_conference">Gå med i konferens</string> <string name="delete_contact">Ta bort kontakt</string> <string name="view_contact_details">Se kontaktdetaljer</string> <string name="block_contact">Blockera kontakt</string> <string name="unblock_contact">Avblockera kontakt</string> <string name="create">Skapa</string> + <string name="select">Välj</string> <string name="contact_already_exists">Kontakten finns redan</string> <string name="join">Gå med</string> <string name="conference_address">Konferensadress</string> @@ -225,6 +241,7 @@ <string name="you">Du</string> <string name="action_edit_subject">Ändra konferensämne</string> <string name="conference_not_found">Konferens hittades inte</string> + <string name="conference_unknown_error">Mottog okänt fel</string> <string name="leave">Lämna</string> <string name="contact_added_you">Kontakten lade till dig i sin kontaktlista</string> <string name="add_back">Addera tillbaka</string> @@ -249,7 +266,6 @@ <string name="skip">Hoppa över</string> <string name="disable_notifications">Inaktivera notifieringar</string> <string name="disable_notifications_for_this_conversation">Inaktivera notifieringar för denna konversation</string> - <string name="notifications_disabled">Notifieringar är inaktiverade</string> <string name="enable">Aktivera</string> <string name="conference_requires_password">Konferensen kräver lösenord</string> <string name="enter_password">Fyll i lösenord</string> @@ -260,9 +276,11 @@ <string name="sure_delete_fingerprint">Är du säker på att du vill ta bort detta fingeravtryck?</string> <string name="ignore">Ignorera</string> <string name="without_mutual_presence_updates"><b>Varning:</b> Skicka detta utan gemensamma tillgänglighetsuppdateringar kan ge oväntade problem.\n\n<small>Gå till kontaktdetaljer för att verifiera dina tillgänglighetsuppdateringar.</small></string> - <string name="pref_encryption_settings">Krypteringsinställningar</string> + <string name="pref_security_settings">Säkerhet</string> <string name="pref_force_encryption">Tvinga kryptering</string> <string name="pref_force_encryption_summary">Sänd alltid krypterade meddelanden (utom för konferenser)</string> + <string name="pref_allow_message_correction">Tillåt korrigeringar av meddelanden</string> + <string name="pref_allow_message_correction_summary">Tillåt att dina kontakter kan ändra sina meddelanden i efterhand</string> <string name="pref_dont_save_encrypted">Spara inte krypterade meddelanden</string> <string name="pref_dont_save_encrypted_summary">Varning: Detta kan leda till att meddelanden förloras</string> <string name="pref_expert_options">Expertinställningar</string> @@ -283,7 +301,10 @@ <string name="pref_expert_options_other">Annat</string> <string name="pref_conference_name">Konferensnamn</string> <string name="pref_conference_name_summary">Använd konferensens ämne istället för JID för att identifiera konferenser</string> + <string name="pref_autojoin">Anslut till konferenser automatiskt</string> + <string name="pref_autojoin_summary">Respektera flaggan för automatisk anslutning till konferenser i bokmärkena</string> <string name="toast_message_otr_fingerprint">OTR-fingeravtryck har kopierats till urklipp!</string> + <string name="toast_message_omemo_fingerprint">OMEMO-fingeravtryck har kopierats till urklipp!</string> <string name="conference_banned">Du är bannlyst från denna konferens</string> <string name="conference_members_only">Medlemsskap krävs för denna konferens</string> <string name="conference_kicked">Du har blivit utsparkad från denna konferens</string> @@ -295,7 +316,7 @@ <string name="copy_text">Kopiera text</string> <string name="copy_original_url">Kopiera orginal-URL</string> <string name="send_again">Skicka igen</string> - <string name="file_url">Fil URL</string> + <string name="file_url">Fil-URL</string> <string name="message_text">Meddelandetext</string> <string name="url_copied_to_clipboard">URL kopierad till urklipp</string> <string name="message_copied_to_clipboard">Meddelande kopierat till urklipp</string> @@ -307,7 +328,6 @@ <string name="verify_otr">Verifiera OTR</string> <string name="remote_fingerprint">Fjärr-fingeravtryck</string> <string name="scan">skanna</string> - <string name="or_touch_phones">(eller nudda telefoner)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Ledtråd eller fråga</string> <string name="shared_secret_secret">Delad hemlighet</string> @@ -319,11 +339,14 @@ <string name="try_again">Försök igen</string> <string name="finish">Klar</string> <string name="verified">Verifierad</string> - <string name="smp_requested">Kontakt begärde SMP verifikation</string> + <string name="smp_requested">Kontakt begärde SMP-verifikation</string> <string name="no_otr_session_found">Ingen giltig OTR-session kunde hittas!</string> <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Håll tjänst i förgrunden</string> <string name="pref_keep_foreground_service_summary">Förehindrar operativsystemet att ta ner uppkopplingen</string> + <string name="pref_export_logs">Exportera loggar</string> + <string name="pref_export_logs_summary">Skriv loggar till SD-kort</string> + <string name="notification_export_logs_title">Skriver loggar till SD-kort</string> <string name="choose_file">Välj fil</string> <string name="receiving_x_file">Tar emot %1$s (%2$d%% klart)</string> <string name="download_x_file">Ladda ner %s</string> @@ -350,6 +373,17 @@ <string name="reset">Återställ</string> <string name="account_image_description">Kontots avatarbild</string> <string name="copy_otr_clipboard_description">Kopiera OTR-fingeravtryck till urklipp</string> + <string name="copy_omemo_clipboard_description">Kopiera OMEMO-fingeravtryck till urklipp</string> + <string name="regenerate_omemo_key">Regenerera OMEMO-nyckel</string> + <string name="wipe_omemo_pep">Rensa andra enheter från PEP</string> + <string name="clear_other_devices">Rensa enheter</string> + <string name="clear_other_devices_desc">Är du säker på att du vill rensa alla andra enheter från OMEMO-annonsering? Nästa gång dina enheter ansluter kommer de att återannonsera sig, men de kanske inte tar emot enheter under tiden.</string> + <string name="purge_key">Rensa nyckel</string> + <string name="purge_key_desc_part1">Är du säker på att du vill rensa denna nyckel?</string> + <string name="purge_key_desc_part2">Den kommer att antas oåterkalleligt komprometterat och du kommer aldrig kunna bygga en session med den igen.</string> + <string name="error_no_keys_to_trust_server_error">Det finns inga användbara nycklar tillgängliga för den här kontakten.\nHämtning av nya nyckar från servern har inte lyckats. Kanske är det något fel på din kontakts server.</string> + <string name="error_no_keys_to_trust">Det finns inga användbara nycklar tillgängliga för den här kontakten. Om du har rensat någon av dennes nycklar behöver de generera nya.</string> + <string name="error_trustkeys_title">Fel</string> <string name="fetching_history_from_server">Hämtar historik från server</string> <string name="no_more_history_on_server">Ingen mer historik på server</string> <string name="updating">Uppdaterar…</string> @@ -387,8 +421,10 @@ <string name="public_conference">Publikt tillgänglig konferens</string> <string name="private_conference">Privat konferens där medlemsskap krävs</string> <string name="conference_options">Konferensalternativ</string> - <string name="members_only">Privat (endast för medlemmar)</string> + <string name="members_only">Privat, medlemsskap krävs</string> <string name="non_anonymous">Icke-anonymt</string> + <string name="moderated">Modererad</string> + <string name="you_are_not_participating">Du deltar ej</string> <string name="modified_conference_options">Ändrade konferensalternativ</string> <string name="could_not_modify_conference_options">Kunde inte ändra konferensalternativ</string> <string name="never">Aldrig</string> @@ -397,7 +433,7 @@ <string name="two_hours">2 timmar</string> <string name="eight_hours">8 timmar</string> <string name="until_further_notice">Tills vidare</string> - <string name="pref_input_options">Inmatningsalternativ</string> + <string name="pref_input_options">Input</string> <string name="pref_enter_is_send">Skicka med enter</string> <string name="pref_enter_is_send_summary">Använd enter-knappen för att skicka meddelande</string> <string name="pref_display_enter_key">Visa enter-knappen</string> @@ -406,7 +442,7 @@ <string name="video">video</string> <string name="image">bild</string> <string name="pdf_document">PDF-dokument</string> - <string name="apk">Android App</string> + <string name="apk">Android-app</string> <string name="vcard">Kontakt</string> <string name="received_x_file">Tagit emot %s</string> <string name="disable_foreground_service">Deaktivera förgrundstjänst</string> @@ -427,7 +463,6 @@ <string name="received_location">Mottog position</string> <string name="title_undo_swipe_out_conversation">Konversation stängd</string> <string name="title_undo_swipe_out_muc">Lämnade konferens</string> - <string name="pref_certificate_options">Certifikatalternativ</string> <string name="pref_dont_trust_system_cas_title">Lita inte på systemets CAs</string> <string name="pref_dont_trust_system_cas_summary">Alla certifikat måste manuellt godkännas</string> <string name="pref_remove_trusted_certificates_title">Ta bort certifikat</string> @@ -449,6 +484,75 @@ <string name="none">Ingen</string> <string name="recently_used">Senast använd</string> <string name="choose_quick_action">Välj snabbfunktion</string> - <string name="file_not_found_on_remote_host">Filen hittas ej på servern</string> <string name="search_for_contacts_or_groups">Sök efter kontakter eller grupper</string> + <string name="send_private_message">Skicka privat meddelande</string> + <string name="user_has_left_conference">%s har lämnat konferensen!</string> + <string name="username">Användarnamn</string> + <string name="username_hint">Användarnamn</string> + <string name="invalid_username">Inte ett giltigt användanamn</string> + <string name="download_failed_server_not_found">Nerladdning gick fel: Server hittades inte</string> + <string name="download_failed_file_not_found">Nerladdning gick fel: Filen hittades inte</string> + <string name="download_failed_could_not_connect">Nerladdningen gick fel: Kunder inte ansluta till server</string> + <string name="account_status_tor_unavailable">Tor-nätverk ej tillgängligt</string> + <string name="server_info_broken">Sönder</string> + <string name="pref_presence_settings">Tillgänglighet</string> + <string name="pref_away_when_screen_off">Status borta när skärmen är av</string> + <string name="pref_away_when_screen_off_summary">Sätter din tillgänglighet till borta när skrämen är av</string> + <string name="pref_xa_on_silent_mode">Status ej tillgänglig i tyst läge</string> + <string name="pref_xa_on_silent_mode_summary">Sätter din tillgänglighet till ej tillgänglig när enheten är i tyst läge</string> + <string name="pref_show_connection_options">Utökade anslutningsinställningar</string> + <string name="pref_show_connection_options_summary">Visa val av servernamn och port vid inställning av konto</string> + <string name="hostname_example">xmpp.example.com</string> + <string name="action_add_account_with_certificate">Lägg till konto med certifikat</string> + <string name="unable_to_parse_certificate">Kan inte läsa certifikat</string> + <string name="authenticate_with_certificate">Lämna tom för att för att logga in med certifikat</string> + <string name="mam_prefs">Arkiveringsinställningar</string> + <string name="server_side_mam_prefs">Arkiveringsinställningar på servern</string> + <string name="fetching_mam_prefs">Hämtar arkiveringsinställningar, vänta...</string> + <string name="unable_to_fetch_mam_prefs">Kan ej hämta arkiveringsinställningar</string> + <string name="captcha_ocr">CAPTCHA-text</string> + <string name="captcha_required">CAPTCHA krävs</string> + <string name="captcha_hint">skriv in texten från bilden</string> + <string name="certificate_chain_is_not_trusted">Certifikatskedjan är inte betrodd</string> + <string name="jid_does_not_match_certificate">Jabber-ID matchar inte certifikat</string> + <string name="action_renew_certificate">Förnya certifikat</string> + <string name="error_fetching_omemo_key">Misslyckades med att hämta OMEMO-nyckel!</string> + <string name="verified_omemo_key_with_certificate">Verifierade OMEMO-nyckel med certifikat!</string> + <string name="device_does_not_support_certificates">Din enhet stödjer inte val av klientcertifikat!</string> + <string name="pref_connection_options">Anslutning</string> + <string name="account_settings_hostname">Servernamn</string> + <string name="account_settings_port">Port</string> + <string name="not_a_valid_port">Inte ett giltigt portnummer</string> + <string name="not_valid_hostname">Inte ett giltigt servernamn</string> + <string name="connected_accounts">%1$d av %2$d konton anslutna</string> + <plurals name="x_messages"> + <item quantity="one">%d meddelande</item> + <item quantity="other">%d meddelanden</item> + </plurals> + <string name="shared_file_with_x">Delade fil med %s</string> + <string name="shared_image_with_x">Delade bild med %s</string> + <string name="no_storage_permission">Conversations behöver access till extern lagring</string> + <string name="sync_with_contacts">Synkronisera med kontakter</string> + <string name="sync_with_contacts_long">Conversations vill matcha din XMPP-kontaktlista med dina kontakter för att visa deras namn och profilbild.\n\nConversations läser endast dina kontakter för att matcha dem lokalt utan att ladda upp dem till din server.\n\nDu kommer nu få frågan om tillåtelse för att använda kontakterna.</string> + <string name="certificate_information">Certifikatinformation</string> + <string name="certificate_subject">Ämne</string> + <string name="certificate_issuer">Utfärdare</string> + <string name="certificate_cn">Common Name</string> + <string name="certificate_o">Organisation</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(Ej tillgänglig)</string> + <string name="certificate_not_found">Inget certifikat funnet</string> + <string name="notify_on_all_messages">Notifiera för alla meddelanden</string> + <string name="notify_only_when_highlighted">Notifiera endast vid highlight</string> + <string name="notify_never">Notifieringar deaktiverade</string> + <string name="notify_paused">Notifieringar pausade</string> + <string name="always">Alltid</string> + <string name="automatically">Automatiskt</string> + <string name="battery_optimizations_enabled">Batterioptimeringar aktiverade</string> + <string name="battery_optimizations_enabled_explained">Din enhet har kraftiga batterioptimeringar som påverkar Conversations på så sätt att inkommande meddelanden kan försenas eller kan till och med gå förlorade.\nDet är rekommenderat att deaktivera batterioptimeringarna.</string> + <string name="battery_optimizations_enabled_dialog">Din enhet har kraftiga batterioptimeringar som påverkar Conversations på så sätt att inkommande meddelanden kan försenas eller kan till och med gå förlorade.\nDu kommer nu att bli ombedd att stänga av batterioptimeringarna för Conversations.</string> + <string name="disable">Deaktivera</string> + <string name="selection_too_large">The valda området är för stort</string> + <string name="no_accounts">(Inget konto aktiverat)</string> + <string name="this_field_is_required">Detta fält måste fyllas i</string> </resources> diff --git a/src/main/res/values-tr-rTR/strings.xml b/src/main/res/values-tr-rTR/strings.xml new file mode 100644 index 00000000..c96ab61c --- /dev/null +++ b/src/main/res/values-tr-rTR/strings.xml @@ -0,0 +1,555 @@ +<?xml version='1.0' encoding='UTF-8'?> +<resources> + <string name="action_settings">Ayarlar</string> + <string name="action_add">Yeni sohbet</string> + <string name="action_accounts">Hesapları yönet</string> + <string name="action_end_conversation">Sohbeti sonlandır</string> + <string name="action_contact_details">Kişi bilgileri</string> + <string name="action_muc_details">Grup sohbet bilgileri</string> + <string name="action_secure">Güvenli sohbet</string> + <string name="action_add_account">Hesap ekle</string> + <string name="action_edit_contact">İsmi düzenle</string> + <string name="action_add_phone_book">Telefon rehberine ekle</string> + <string name="action_delete_contact">Kişi listesinden sil</string> + <string name="action_block_contact">Kişiyi engelle</string> + <string name="action_unblock_contact">Kişiyi engellemekten vazgeç</string> + <string name="action_block_domain">Alan adını engelle</string> + <string name="action_unblock_domain">Alan adını engellemekten vazgeç</string> + <string name="title_activity_manage_accounts">Hesapları yönet</string> + <string name="title_activity_settings">Ayarlar</string> + <string name="title_activity_conference_details">Grup sohbet bilgileri</string> + <string name="title_activity_contact_details">Kişi bilgileri</string> + <string name="title_activity_sharewith">Sohbetle paylaş</string> + <string name="title_activity_start_conversation">Sohbeti başlat</string> + <string name="title_activity_choose_contact">Kişi seç</string> + <string name="title_activity_block_list">Listeyi blokla</string> + <string name="just_now">şimdi</string> + <string name="minute_ago">1 dakika önce</string> + <string name="minutes_ago">%d dakika önc</string> + <string name="unread_conversations">okunmamış sohbetler</string> + <string name="sending">gönderiyor…</string> + <string name="message_decrypting">İleti deşifre ediliyor. Lütfen bekleyin…</string> + <string name="pgp_message">OpenPGP şifreli mesaj</string> + <string name="nick_in_use">Rumuz kullanılıyor</string> + <string name="admin">Yönetici</string> + <string name="owner">Sahip</string> + <string name="moderator">Moderatör</string> + <string name="participant">Katılımcı</string> + <string name="visitor">Ziyaretçi</string> + <string name="remove_contact_text">%s kişisini listenizden silmek istiyor musunuz? Bu kişiyle yaptığınız sohbetler silinmeyecektir.</string> + <string name="block_contact_text">%s kişisinin size ileti göndermesini engellemek istiyor musunuz?</string> + <string name="unblock_contact_text">% kişisinin size ileti göndermesine koyduğunuz engellemeyi kaldırmak ve size ileti göndermesine izin vermek istiyor musunuz?</string> + <string name="block_domain_text">%s üzerinden gelen bütün kişileri engellemek istiyor musunuz? </string> + <string name="unblock_domain_text">%s üzerinden gelen kişilerdeki engellemeyi kaldırmak istiyor musunuz?</string> + <string name="contact_blocked">Kişi engellendi</string> + <string name="remove_bookmark_text">%s yer imini silmek istiyor musunuz? Bu yer imiyle ilintili sohbet silinmeyecektir.</string> + <string name="register_account">Sunucuda yeni bir hesap oluştur</string> + <string name="change_password_on_server">Sunucudaki şifreni değiştir</string> + <string name="share_with">Paylaş...</string> + <string name="start_conversation">Sohbet başlat</string> + <string name="invite_contact">Kişi davet et</string> + <string name="contacts">Kişiler</string> + <string name="cancel">İptal et</string> + <string name="set">Ayarla</string> + <string name="add">Ekle</string> + <string name="edit">Düzenle</string> + <string name="delete">Sil</string> + <string name="block">Engelle</string> + <string name="unblock">Engellemeyi kaldır</string> + <string name="save">Kaydet</string> + <string name="ok">Tamam</string> + <string name="crash_report_title">Conversations çöktü</string> + <string name="crash_report_message">Çöküş raporu göndermeniz Conversations\n’ın geliştirilmesine katkıda bulunacaktır.<b>Uyarı:</b>Bu rapor yazılım geliştiriciye XMPP hesabınız üzerinden gönderilecektir.</string> + <string name="send_now">Şimdi gönder</string> + <string name="send_never">Bir daha sorma</string> + <string name="problem_connecting_to_account">Hesaba bağlanılamıyor</string> + <string name="problem_connecting_to_accounts">Birden fazla hesaba bağlanılamıyor</string> + <string name="touch_to_fix">Hesaplarınızı yönetmek için dokunun</string> + <string name="attach_file">Dosya ekle</string> + <string name="not_in_roster">Kişi, listenizde değil. Eklemek ister misiniz?</string> + <string name="add_contact">Kişi ekle</string> + <string name="send_failed">ulaştırılamadı</string> + <string name="send_rejected">reddedildi</string> + <string name="preparing_image">Görüntü, iletilmek üzere hazırlanıyor</string> + <string name="action_clear_history">Geçmişi sil</string> + <string name="clear_conversation_history">Sohbet geçmişini sil</string> + <string name="clear_histor_msg">Bu sohbetteki bütün iletileri silmek istiyor musunuz?\n\n<b>Uyarı:</b>Başka cihazlardaki ya da sunuculardaki iletiler bundan etkilenmeyecektir.</string> + <string name="delete_messages">İletileri sil</string> + <string name="also_end_conversation">Sonrasında bu sohbeti sonlandır</string> + <string name="choose_presence">Kaynak kişiyi seçin</string> + <string name="send_unencrypted_message">Şifrelenmemiş ileti gönder</string> + <string name="send_otr_message">OTR ile şifrelenmiş ileti gönder</string> + <string name="send_omemo_message">OMEMO ile şifrelenmiş ileti gönder</string> + <string name="send_omemo_x509_message">v\\OMEMO ile şifrelenmiş ileti gönder</string> + <string name="send_pgp_message">OpenPGP ile şifrelenmiş ileti gönder</string> + <string name="your_nick_has_been_changed">Rumuzunuz değişti</string> + <string name="send_unencrypted">Şifrelenmemiş gönder</string> + <string name="decryption_failed">Deşifre edilemedi. Uygun bir özel anahtarınız olmayabilir.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations iletileri şifreleyip deşifre etmek ve ortak anahtarlarınızı yönetmek için bir üçüncü parti uygulaması olan <b>OpenKeychain’i</b> kullanmaktadır. \n\nOpenKeychain GPLv3 altında lisanslı olup F-Droid ve Google Play’den indirilebilir.\n\n<small>(Lütfen daha sonra Conversations’ı yeniden başlatın.)</small></string> + <string name="restart">Yeniden başlat</string> + <string name="install">Kur</string> + <string name="openkeychain_not_installed">Lütfen OpenKeychain’i kur</string> + <string name="offering">sunuluyor…</string> + <string name="waiting">bekliyor…</string> + <string name="no_pgp_key">Herhangi bir OpenPGP anahtarı bulunamadı</string> + <string name="contact_has_no_pgp_key">Kişi ortak anahtarını yayınlamadığı için Conversations iletilerinizi şifreleyemiyor.\n\n<small>Lütfen kişiden OpenPGP’yi ayarlamasını isteyin.</small></string> + <string name="no_pgp_keys">Herhangi bir OpenPGP anahtarı bulunamadı</string> + <string name="contacts_have_no_pgp_keys">Kişiler ortak anahtarlarını yayınlamadığı için Conversations iletilerinizi şifreleyemiyor.\n\n<small>Lütfen kişilerden OpenPGP’yi ayarlamalarını isteyin.</small></string> + <string name="encrypted_message_received"><i>Şifreli ileti alındı. Deşifre etmek için dokunun.</i></string> + <string name="pref_general">Genel</string> + <string name="pref_xmpp_resource">XMPP kaynağı</string> + <string name="pref_xmpp_resource_summary">İstemci kimliği</string> + <string name="pref_accept_files">Dosyaları kabul et</string> + <string name="pref_accept_files_summary">…‘den küçük olan dosyaları otomatik olarak kabul et</string> + <string name="pref_notification_settings">Bildirim</string> + <string name="pref_notifications">Bildirimler</string> + <string name="pref_notifications_summary">Yeni ileti geldiğinde bildir</string> + <string name="pref_vibrate">Titreşim</string> + <string name="pref_vibrate_summary">Yeni bir ileti geldiğinde aynı zamanda titreşsin</string> + <string name="pref_sound">Ses</string> + <string name="pref_sound_summary">Bildirimle birlikte zil çalsın</string> + <string name="pref_notification_grace_period">Bildirim mühleti</string> + <string name="pref_notification_grace_period_summary">Karbon kopya alındıktan sonra kısa bir süre için bildirimleri kapa</string> + <string name="pref_advanced_options">Gelişmiş</string> + <string name="pref_never_send_crash">Asla çöküş raporu gönderme</string> + <string name="pref_never_send_crash_summary">Çöküş raporu göndermeniz Conversations\n’ın geliştirilmesine katkıda bulunacaktır.</string> + <string name="pref_confirm_messages">İletileri onayla</string> + <string name="pref_confirm_messages_summary">Karşı tarafa ileti alındı ve okundu raporu gönder.</string> + <string name="pref_ui_options">Arabirim</string> + <string name="openpgp_error">OpenKeychain bir hata bildirdi</string> + <string name="error_decrypting_file">Dosyanın deşifresinde G/Ç hatası</string> + <string name="accept">Kabul et</string> + <string name="error">Bir hata oluştu</string> + <string name="pref_grant_presence_updates">Durum güncelleme bildirimlerine izin ver</string> + <string name="pref_grant_presence_updates_summary">Eklediğiniz kişilerin durum bildirimlerine abone olmayı isteyin ve peşinen kabul edin.</string> + <string name="subscriptions">Abonelikler</string> + <string name="your_account">Hesabınız</string> + <string name="keys">Anahtarlar</string> + <string name="send_presence_updates">Çevrimiçi durum bildirimi gönder</string> + <string name="receive_presence_updates">Çevrimiçi durum bildirimi al</string> + <string name="ask_for_presence_updates">Çevrimiçi durum bildirimi iste</string> + <string name="attach_choose_picture">Resim seç</string> + <string name="attach_take_picture">Resim çek</string> + <string name="preemptively_grant">Abonelik isteğini peşinen kabul et</string> + <string name="error_not_an_image_file">Seçtiğiniz dosya bir görüntü dosyası değil</string> + <string name="error_compressing_image">Görüntü dosyasını dönüştürürken hata oluştu</string> + <string name="error_file_not_found">Dosya bulunamadı</string> + <string name="error_io_exception">Genel G/Ç hatası. Depolama yeri kalmamış olabilir mi?</string> + <string name="error_security_exception_during_image_copy">Bu resmi seçmek için kullandığınız uygulama, dosyayı okuyabilmemiz için izin vermiyor. \n\n<small>Resim seçmek için farklı bir dosya yöneticisi kullanın.</small></string> + <string name="account_status_unknown">Bilinmeyen</string> + <string name="account_status_disabled">Geçici olarak devre dışı</string> + <string name="account_status_online">Çevrimiçi</string> + <string name="account_status_connecting">Bağlanıyor\u2026</string> + <string name="account_status_offline">Çevrimdışı</string> + <string name="account_status_unauthorized">Yetkisiz</string> + <string name="account_status_not_found">Sunucu bulunamadı</string> + <string name="account_status_no_internet">Bağlantı yok</string> + <string name="account_status_regis_fail">Hesap oluşturulamadı</string> + <string name="account_status_regis_conflict">Kullanıcı adı kullanılıyor</string> + <string name="account_status_regis_success">Hesap oluşturuldu</string> + <string name="account_status_regis_not_sup">Sunucu hesap oluşturma işlemini desteklemiyor</string> + <string name="account_status_security_error">Güvenlik hatası</string> + <string name="account_status_incompatible_server">Sunucu uyuşmazlığı</string> + <string name="encryption_choice_unencrypted">Şifrelenmemiş</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Hesabı düzenle</string> + <string name="mgmt_account_delete">Hesabı sil</string> + <string name="mgmt_account_disable">Geçici olarak devre dışı bırak</string> + <string name="mgmt_account_publish_avatar">Avatar yayınla</string> + <string name="mgmt_account_publish_pgp">OpenPGP genel anahtarını yayınla</string> + <string name="mgmt_account_enable">Hesabı etkinleştir</string> + <string name="mgmt_account_are_you_sure">Emin misiniz?</string> + <string name="mgmt_account_delete_confirm_text">Hesabınızı silerseniz bütün sohbet geçmişiniz silinecek</string> + <string name="attach_record_voice">Ses kaydet</string> + <string name="account_settings_jabber_id">Jabber ID</string> + <string name="account_settings_password">Parola</string> + <string name="account_settings_example_jabber_id">kullanıcıadı@ornek.com</string> + <string name="account_settings_confirm_password">Parolayı doğrula</string> + <string name="password">parola</string> + <string name="confirm_password">Parolayı doğrula</string> + <string name="passwords_do_not_match">Parolalar eşleşmiyor</string> + <string name="invalid_jid">Jabber ID geçersiz</string> + <string name="error_out_of_memory">Yetersiz bellek. Görüntü dosyası çok büyük.</string> + <string name="add_phone_book_text">%s kişisini listenize eklemek ister misiniz?</string> + <string name="contact_status_online">çevrimiçi</string> + <string name="contact_status_free_to_chat">sohbet için uygun</string> + <string name="contact_status_away">uzakta</string> + <string name="contact_status_extended_away">uzun süredir uzakta</string> + <string name="contact_status_do_not_disturb">rahatsız etmeyin</string> + <string name="contact_status_offline">çevrimdışı</string> + <string name="muc_details_conference">Grup Sohbet</string> + <string name="muc_details_other_members">Diğer Üyeler</string> + <string name="server_info_show_more">Sunucu bilgisi</string> + <string name="server_info_mam">XEP-0313: MAM</string> + <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> + <string name="server_info_csi">XEP-0352: Client State Indication</string> + <string name="server_info_blocking">XEP-0191: Blocking Command</string> + <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> + <string name="server_info_stream_management">XEP-0198: Stream Management</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> + <string name="server_info_push">XEP-0357: Push</string> + <string name="server_info_available">mevcut</string> + <string name="server_info_unavailable">mevcut değil</string> + <string name="missing_public_keys">Kayıp genel anahtar bildirimleri</string> + <string name="last_seen_now">en son şimdi görüldü</string> + <string name="last_seen_min">en son 1 dakika önce görüldü</string> + <string name="last_seen_mins">en son %d dakika önce görüldü</string> + <string name="last_seen_hour">en son 1 saat önce görüldü</string> + <string name="last_seen_hours">en son %d saat önce görüldü</string> + <string name="last_seen_day">en son 1 gün önce görüldü</string> + <string name="last_seen_days">en son %d gün önce görüldü</string> + <string name="never_seen">hiç görülmedi</string> + <string name="install_openkeychain">Şifreli mesaj. Deşifre etmek için lütfen OpenKeychain kurun.</string> + <string name="unknown_otr_fingerprint">Bilinmeyen OTR parmak izi</string> + <string name="openpgp_messages_found">OpenPGP ile şifrelenmiş iletiler bulundu</string> + <string name="reception_failed">Alınamadı</string> + <string name="your_fingerprint">Parmak iziniz</string> + <string name="otr_fingerprint">OTR parmak izi</string> + <string name="omemo_fingerprint">OMEMO parmak izi</string> + <string name="omemo_fingerprint_x509">v\\OMEMO parmak izi</string> + <string name="omemo_fingerprint_selected_message">İletinin OMEMO parmak izi</string> + <string name="omemo_fingerprint_x509_selected_message">v\\İletinin OMEMO parmak izi</string> + <string name="this_device_omemo_fingerprint">OMEMO parmak iziniz</string> + <string name="other_devices">Diğer cihazlar</string> + <string name="trust_omemo_fingerprints">OMEMO parmak izlerine güven</string> + <string name="fetching_keys">Anahtarları alıyor…</string> + <string name="done">Tamam</string> + <string name="verify">Doğrula</string> + <string name="decrypt">Deşifre et</string> + <string name="conferences">Grup Sohbetleri</string> + <string name="search">Ara</string> + <string name="create_contact">Kişi Oluştur</string> + <string name="enter_contact">Kişi Girin</string> + <string name="join_conference">Grup Sohbete Katıl</string> + <string name="delete_contact">Kişi Sil</string> + <string name="view_contact_details">Kişi bilgilerini görüntüle</string> + <string name="block_contact">Kişiyi engelle</string> + <string name="unblock_contact">Kişiyi engellemeyi kaldır</string> + <string name="create">Oluştur</string> + <string name="select">Seç</string> + <string name="contact_already_exists">Kişi zaten mevcut</string> + <string name="join">Katıl</string> + <string name="conference_address">Grup sohbet adresi</string> + <string name="conference_address_example">oda@conference.ornek.com</string> + <string name="save_as_bookmark">Yer imi olarak kaydet</string> + <string name="delete_bookmark">Yer imini sil</string> + <string name="bookmark_already_exists">Bu yer imi zaten mevcut</string> + <string name="you">Siz</string> + <string name="action_edit_subject">Grup sohbet konusunu düzenle</string> + <string name="conference_not_found">Grup sohbet bulunamadı</string> + <string name="conference_unknown_error">Bilinmeyen hata alındı</string> + <string name="leave">Ayrıl</string> + <string name="contact_added_you">Kişi sizi listesine ekledi</string> + <string name="add_back">Siz de ekleyin</string> + <string name="contact_has_read_up_to_this_point">%s buraya kadar okudu</string> + <string name="publish">Yayınla</string> + <string name="touch_to_choose_picture">Galeriden resim seçmek için avatara dokun</string> + <string name="publish_avatar_explanation">Lütfen dikkat: Çevrimiçi durum bildirimi güncellemelerinize abone olan herkes bu resmi görebilir.</string> + <string name="publishing">Yayınlanıyor…</string> + <string name="error_publish_avatar_server_reject">Sunucu yayınladığınız resmi reddetti</string> + <string name="error_publish_avatar_converting">Resim dönüştürülürken hata oluştu</string> + <string name="error_saving_avatar">vatar diske kaydedilemedi</string> + <string name="or_long_press_for_default">(Veya varsayılan değerlere dönmek için uzun süre basılı tutun)</string> + <string name="error_publish_avatar_no_server_support">Sunucunuz avatar yayınlanmasını desteklemiyor</string> + <string name="private_message">fısıldandı</string> + <string name="private_message_to">%s kişisine</string> + <string name="send_private_message_to">%s kişisine özel mesaj gönder</string> + <string name="connect">Bağlan</string> + <string name="account_already_exists">Bu hesap zaten mevcut</string> + <string name="next">Sonraki</string> + <string name="server_info_session_established">Oturum sağlandı</string> + <string name="additional_information">Ek bilgi</string> + <string name="skip">Atla</string> + <string name="disable_notifications">Bildirimleri kapat</string> + <string name="disable_notifications_for_this_conversation">Bu sohbet için bildirimleri kapat</string> + <string name="enable">Etkinleştir</string> + <string name="conference_requires_password">Grup sohbet için parola gerekiyor</string> + <string name="enter_password">Parolayı gir</string> + <string name="missing_presence_updates">Kişinin çevrimiçi durum bildirimi güncellemesi kayıp</string> + <string name="request_presence_updates">Lütfen öncelikle kişiden çevrimiçi durum güncellemelerini isteyin.\n\n<small>Bu bilgi kişinin kullandığı istemcinin belirlenmesinde kullanılacaktır.</small></string> + <string name="request_now">Şimdi iste</string> + <string name="delete_fingerprint">Parmak izini sil</string> + <string name="sure_delete_fingerprint">Bu parmak izini silmek istediğinizden emin misiniz?</string> + <string name="ignore">Yok say</string> + <string name="without_mutual_presence_updates"><b>Uyarı:</b> Karşılıklı çevrimiçi durum bildirimi güncellemeleri olmaksızın bunu göndermeniz beklenmedik sorunlara sebep olabilir.\n\n\n\n<small>Çevrimiçi durum bildirimi aboneliklerinizi kontrol etmek için kişi bilgilerine gidin.</small></string> + <string name="pref_security_settings">Güvenlik</string> + <string name="pref_force_encryption">Uçtan uca şifrelemeye zorla</string> + <string name="pref_force_encryption_summary">Her zaman şifrelenmiş ileti gönder (Conversations hariç)</string> + <string name="pref_allow_message_correction">İleti düzeltmeye izin ver</string> + <string name="pref_allow_message_correction_summary">Kişilerinizin geçmiş iletilerini düzeltmelerine izin ver</string> + <string name="pref_dont_save_encrypted">Şifrelenmiş iletileri kaydetme</string> + <string name="pref_dont_save_encrypted_summary">Uyarı: Bu, iletilerin kaybedilmesine neden olabilir</string> + <string name="pref_expert_options">Uzman seçenekleri</string> + <string name="pref_expert_options_summary">Lütfen dikkatli olun</string> + <string name="title_activity_about">Conversations hakkında</string> + <string name="pref_about_conversations_summary">Geliştirme ve lisans bilgisi</string> + <string name="title_pref_quiet_hours">Sessiz Saatler</string> + <string name="title_pref_quiet_hours_start_time">Başlangıç zamanı</string> + <string name="title_pref_quiet_hours_end_time">Bitiş zamanı</string> + <string name="title_pref_enable_quiet_hours">Sessiz saatleri etkinleştir</string> + <string name="pref_quiet_hours_summary">Bildirimler sessiz saatler boyunca sessize alınacaktır</string> + <string name="pref_use_larger_font">Fontu büyüt</string> + <string name="pref_use_larger_font_summary">Uygulamanın tamamında daha büyük font kullan+</string> + <string name="pref_use_send_button_to_indicate_status">Gönder düğmesi durum bildirsin</string> + <string name="pref_use_indicate_received">İleti alındısı iste</string> + <string name="pref_use_indicate_received_summary">Alınan mesajlar, eğer destekleniyorsa, yeşil bir tikle işaretlenecektir.</string> + <string name="pref_use_send_button_to_indicate_status_summary">Gönder butonunu kişinin durumuna göre renklendir</string> + <string name="pref_expert_options_other">Diğer</string> + <string name="pref_conference_name">Grup sohbet ismi</string> + <string name="pref_conference_name_summary">Grup sohbetleri tanımlamak için JID yerine odanın konusunu kullan</string> + <string name="pref_autojoin">Grup sohbet\'e otomatik olarak katıl</string> + <string name="pref_autojoin_summary">Grup sohbet yer imlerinde otomatik katıl bayrağına riayet et</string> + <string name="toast_message_otr_fingerprint">OTR parmak izi panoya kopyalandı!</string> + <string name="toast_message_omemo_fingerprint">OMEMO parmak izi panoya kopyalandı!</string> + <string name="conference_banned">Grup sohbetinden atıldınız</string> + <string name="conference_members_only">Bu grup sohbet sadece üyelere açıktır</string> + <string name="conference_kicked">Grup sohbetinden atıldınız</string> + <string name="using_account">%s hesabını kullanarak</string> + <string name="checking_x">HTTP sunucusundaki %s \'leri kontrol ediyor</string> + <string name="not_connected_try_again">Bağlı değilsiniz. Daha sonra yeniden deneyin</string> + <string name="check_x_filesize">%s boyutunu kontrol edin</string> + <string name="message_options">İleti seçenekleri</string> + <string name="copy_text">Metni kopyala</string> + <string name="copy_original_url">Orijinal URL\'i kopyala</string> + <string name="send_again">Yeniden gönder</string> + <string name="file_url">Dosya URL</string> + <string name="message_text">İleti metni</string> + <string name="url_copied_to_clipboard">Panoya kopyalanan URL</string> + <string name="message_copied_to_clipboard">Panoya kopyalanan ileti</string> + <string name="image_transmission_failed">Resim aktarılamadı</string> + <string name="scan_qr_code">QR kodunu tara</string> + <string name="show_qr_code">QR kodunu göster</string> + <string name="show_block_list">Engellenenler listesini göster</string> + <string name="account_details">Hesap bilgileri</string> + <string name="verify_otr">OTR doğrula</string> + <string name="remote_fingerprint">Uzak parmak izi</string> + <string name="scan">tara</string> + <string name="smp">Sosyalist Milyoner Protokolü</string> + <string name="shared_secret_hint">İpucu ya da Soru</string> + <string name="shared_secret_secret">Paylaşılan Gizli Bilgi</string> + <string name="confirm">Doğrula</string> + <string name="in_progress">İşleniyor</string> + <string name="respond">Yanıt</string> + <string name="failed">Başarısız</string> + <string name="secrets_do_not_match">Gizli bilgiler eşleşmiyor</string> + <string name="try_again">Yeniden deneyin</string> + <string name="finish">Bitti</string> + <string name="verified">Doğrulandı!</string> + <string name="smp_requested">Kişi SMP doğrulaması istedi</string> + <string name="no_otr_session_found">Geçerli bir OTR oturumu bulunamadı!</string> + <string name="conversations_foreground_service">Conversations</string> + <string name="pref_keep_foreground_service">Ön planda çalışmaya devam etsin</string> + <string name="pref_keep_foreground_service_summary">İşletim sisteminin bağlantınızı koparmasına engel olur</string> + <string name="pref_export_logs">Kayıtları dışa aktar</string> + <string name="pref_export_logs_summary">Kayıtları SD karta yaz</string> + <string name="notification_export_logs_title">Kayıtları SD karta yazıyor</string> + <string name="choose_file">Dosya seç</string> + <string name="receiving_x_file">%1$s alıyor/(%2$d%% tamamlandı)</string> + <string name="download_x_file">%s indir</string> + <string name="file">dosya</string> + <string name="open_x_file">%s aç</string> + <string name="sending_file">gönderiyor (%1$d%% tamamlandı)</string> + <string name="preparing_file">Dosya, aktarma için hazırlanıyor</string> + <string name="x_file_offered_for_download">%s indirme işlemi için sunuldu</string> + <string name="cancel_transmission">Aktarmayı iptal et</string> + <string name="file_transmission_failed">dosya aktarma başarısız oldu</string> + <string name="file_deleted">Dosya silindi</string> + <string name="no_application_found_to_open_file">Dosyayı açacak bir uygulama bulunamadı</string> + <string name="could_not_verify_fingerprint">Parmak izi doğrulanamadı</string> + <string name="manually_verify">Bizzat doğrula</string> + <string name="are_you_sure_verify_fingerprint">Kişilerin OTR parmak izlerini doğrulamak istediğinizden emin misiniz?</string> + <string name="pref_show_dynamic_tags">Dinamik etiketleri göster</string> + <string name="pref_show_dynamic_tags_summary">Kişilerin görünmeyen salt okunur etiketlerini göster</string> + <string name="enable_notifications">Bildirimleri etkinleştir</string> + <string name="conference_with">… ile grup sohbet başlat</string> + <string name="no_conference_server_found">Bir grup sohbet sunucusu bulunamadı</string> + <string name="conference_creation_failed">Grup sohbet başlatılamadı!</string> + <string name="conference_created">Grup sohbet başladıldı!</string> + <string name="secret_accepted">Gizli bilgi kabul edildi!</string> + <string name="reset">Sıfırla</string> + <string name="account_image_description">Hesap avatarı</string> + <string name="copy_otr_clipboard_description">OTR parmak izini panoya kopyala</string> + <string name="copy_omemo_clipboard_description">OMEMO parmak izini panoya kopyala</string> + <string name="regenerate_omemo_key">OMEMO anahtarını yeniden oluştur</string> + <string name="wipe_omemo_pep">PEP’teki diğer cihazları sil</string> + <string name="clear_other_devices">Cihazları sil</string> + <string name="clear_other_devices_desc">OMEMO bildirimindeki diğer cihazların hepsini silmek istediğinizden emin misiniz? Cihazlarınız yeniden bağlandıklarında kendilerini yeniden bildirecekler ama bu süre zarfındaki iletileri alamayabilirler.</string> + <string name="purge_key">Anahtarı sil</string> + <string name="purge_key_desc_part1">Bu anahtarı silmek istediğinizden emin misiniz?</string> + <string name="purge_key_desc_part2">Anahtar geri dönüşü olmayacak şekilde zedelenmiş kabul edilecek bir daha onunla bir oturum başlatamayacaksınız.</string> + <string name="error_no_keys_to_trust_server_error">Bu kişi için kullanılabilr anahtar mevcut değil\nSunucudan yeni anahtarlar alınamadı. Sunucunuzla ilgili bir sorun olabilir.</string> + <string name="error_no_keys_to_trust">Bu kişi için kullanılabilecek bir anahtar bulunmuyor. Eğer anahtarlarını sildiyseniz, yeni anahtar oluşturmaları gerekiyor.</string> + <string name="error_trustkeys_title">Hata</string> + <string name="fetching_history_from_server">Sunucudan geçmiş alınıyor</string> + <string name="no_more_history_on_server">Sunucuda başka geçmiş kalmadı</string> + <string name="updating">Güncelleniyor…</string> + <string name="password_changed">Parola değişti!</string> + <string name="could_not_change_password">Parola değiştirilemedi</string> + <string name="otr_session_not_started">Şifreli bir konuşma başlatmak için ileti gönder</string> + <string name="ask_question">Soru sor</string> + <string name="smp_explain_question">Karşınızdaki kişiyle, ikinizden başka kimsenin bilmediği ortak bir sırrınız varsa (aranızdaki bir şaka ya da sadece en son buluştuğunuzda ne yediğiniz gibi) bunu birbirinizin parmak izlerini doğrulamak için kullanabilirsiniz. \n\n Karşınızdaki kişiye bir ipucu veya soru sorabilirsiniz. Cevabın harf duyarlı olması gerekmektedir.</string> + <string name="smp_explain_answer">Kişi parmak izinizi onaylamak için sadece ikinizin bildiği bir şeyi sormak istiyor. Kişi sırrınız hakkında aşağıdaki ipucu veya soruyu gönderdi.</string> + <string name="shared_secret_hint_should_not_be_empty">İpucunuz boş kalamaz</string> + <string name="shared_secret_can_not_be_empty">Sırrınız boş kalamaz</string> + <string name="manual_verification_explanation">Aşağıdaki parmak izini, karşınızdaki kişinin parmak iziyle dikkatlice karşılaştırın. \n\nŞifreli eposta veya telefon gibi güvenilir bir iletişim kanalı üzerinden bilgi alışverişi yapabilirsiniz.</string> + <string name="change_password">Parolayı değiştirin</string> + <string name="current_password">Mevcut parola</string> + <string name="new_password">Yeni parola</string> + <string name="password_should_not_be_empty">Parola boş kalamaz</string> + <string name="enable_all_accounts">Bütün hesapları etkinleştir</string> + <string name="disable_all_accounts">Bütün hesapları devre dışı bırak</string> + <string name="perform_action_with">Kullanarak tamamla</string> + <string name="no_affiliation">Ortaklık yok</string> + <string name="no_role">Rol yok</string> + <string name="outcast">Bağlantısız</string> + <string name="member">Üye</string> + <string name="advanced_mode">Gelişmiş kip</string> + <string name="grant_membership">Üyeliğe onay ver</string> + <string name="remove_membership">Üyeliği geri çevir</string> + <string name="grant_admin_privileges">Yönetici imtiyazlarını kabul et</string> + <string name="remove_admin_privileges">Yönetici imtiyazlarını geri çevir</string> + <string name="remove_from_room">Grup sohbetten at</string> + <string name="could_not_change_affiliation">%s kişisinin ortaklığı değiştirilemedi</string> + <string name="ban_from_conference">Grup sohbetten at</string> + <string name="removing_from_public_conference">%s kişisini herkese açık grup sohbetten atmaya çalışıyorsunuz. Bunu ancak bu kullanıcıyı daimi men ederek yapabilirsiniz.</string> + <string name="ban_now">Şimdi men et</string> + <string name="could_not_change_role">%s kişisinin rolü değiştirilemedi</string> + <string name="public_conference">Herkese açık grup sohbet</string> + <string name="private_conference">Özel, sadece üyelere açık grup sohbet</string> + <string name="conference_options">Grup sohbet seçenekleri</string> + <string name="members_only">Özel, sadece üyeler</string> + <string name="non_anonymous">Anonim olmayan</string> + <string name="moderated">Denetli</string> + <string name="you_are_not_participating">Katılımcı değilsiniz</string> + <string name="modified_conference_options">Değiştirilmiş grup sohbet seçenekleri!</string> + <string name="could_not_modify_conference_options">Grup sohbet seçenekleri değiştirilemedi</string> + <string name="never">Hiçbir zaman</string> + <string name="thirty_minutes">30 dakika</string> + <string name="one_hour">1 saat</string> + <string name="two_hours">2 saat</string> + <string name="eight_hours">8 saat</string> + <string name="until_further_notice">İkinci bildirime kadar</string> + <string name="pref_input_options">Girdi</string> + <string name="pref_enter_is_send">Enter=gönder</string> + <string name="pref_enter_is_send_summary">İleti göndermek için \"enter\" tuşunu kullanın</string> + <string name="pref_display_enter_key">\"Enter\" tuşunu göster</string> + <string name="pref_display_enter_key_summary">İfade ikonu tuşunu \"enter\" tuşu olarak değiştirin</string> + <string name="audio">ses</string> + <string name="video">video</string> + <string name="image">görüntü</string> + <string name="pdf_document">PDF belgesi</string> + <string name="apk">Android uygulaması</string> + <string name="vcard">Kişi</string> + <string name="received_x_file">%s alındı</string> + <string name="disable_foreground_service">Ön planda çalışmasını devre dışı bırak</string> + <string name="touch_to_open_conversations">Conversations’ı başlatmak için dokunun</string> + <string name="avatar_has_been_published">Avatar yayınlandı!</string> + <string name="sending_x_file">%s gönderiliyor</string> + <string name="offering_x_file">%s sunuluyor</string> + <string name="hide_offline">Çevrimdışı gizle</string> + <string name="disable_account">Hesabı devre dışı bırak</string> + <string name="contact_is_typing">%s yazıyor…</string> + <string name="contact_has_stopped_typing">%s yazmayı bıraktı</string> + <string name="pref_chat_states">Yazma bildirimleri</string> + <string name="pref_chat_states_summary">Karşınızdaki kişi sizin yeni bir ileti yazdığınızı görsün</string> + <string name="send_location">Yer bildirimi gönder</string> + <string name="show_location">Yer bildirimi göster</string> + <string name="no_application_found_to_display_location">Yer bildirimi için bir uygulama bulunamadı</string> + <string name="location">Yer</string> + <string name="received_location">Bildirilen yer</string> + <string name="title_undo_swipe_out_conversation">Sohbet sonlandı</string> + <string name="title_undo_swipe_out_muc">Grup sohbetten ayrıldı</string> + <string name="pref_dont_trust_system_cas_title">Sistem sertifikalarına güvenmeyin</string> + <string name="pref_dont_trust_system_cas_summary">Bütün sertifikalar bizzat onaylanmalıdır</string> + <string name="pref_remove_trusted_certificates_title">Sertifikaları kaldır</string> + <string name="pref_remove_trusted_certificates_summary">Bizzat onaylanmış sertifikaları sil</string> + <string name="toast_no_trusted_certs">Bizzat onaylanmış sertifika yok</string> + <string name="dialog_manage_certs_title">Sertifikaları kaldır</string> + <string name="dialog_manage_certs_positivebutton">Seçilenleri sil</string> + <string name="dialog_manage_certs_negativebutton">İptal</string> + <plurals name="toast_delete_certificates"> + <item quantity="other">%d sertifikaları silindi</item> + </plurals> + <plurals name="select_contact"> + <item quantity="other">%d kişiyi seç</item> + </plurals> + <string name="pref_quick_action_summary">Gönder düğmesini kısayol atamasıyla değiştir</string> + <string name="pref_quick_action">Kısayol</string> + <string name="none">Hiçbiri</string> + <string name="recently_used">En son kullanılanlar</string> + <string name="choose_quick_action">Kısayolu seç</string> + <string name="search_for_contacts_or_groups">Kişi veya gruplarda ara</string> + <string name="send_private_message">Özel ileti gönder</string> + <string name="user_has_left_conference">%s görüşmeden ayrıldı!</string> + <string name="username">Kullanıcı adı</string> + <string name="username_hint">Kullanıcı adı</string> + <string name="invalid_username">Kullanıcı adı geçerli değil</string> + <string name="download_failed_server_not_found">İndirme başarısız: Sunucu bulunamadı</string> + <string name="download_failed_file_not_found">İndirme başarısız: Dosya bulunamadı</string> + <string name="download_failed_could_not_connect">İndirme başarısız: Sunucuya bağlanılamadı</string> + <string name="account_status_tor_unavailable">Tor ağına erişilemiyor</string> + <string name="server_info_broken">Bozuk</string> + <string name="pref_presence_settings">Durum</string> + <string name="pref_away_when_screen_off">Ekran kapandığında uzakta</string> + <string name="pref_away_when_screen_off_summary">Ekran kapandığında çevrimiçi durum bildiriminizi uzakta olarak değiştirir</string> + <string name="pref_xa_on_silent_mode">Sessiz moddayken erişilemez</string> + <string name="pref_xa_on_silent_mode_summary">Telefonunuz sessizdeyken, durum bildiriminizi müsait değil olarak değiştirir</string> + <string name="pref_show_connection_options">Genişletilmiş bağlantı seçenekleri</string> + <string name="pref_show_connection_options_summary">Hesap oluştururken sunucu adıyla port seçeneğini göster</string> + <string name="hostname_example">xmpp.ornek.com</string> + <string name="action_add_account_with_certificate">Sertifikalı hesap ekle</string> + <string name="unable_to_parse_certificate">Sertifika çözümlenemedi</string> + <string name="authenticate_with_certificate">w/ sertifikasının kimlik denetimi için boş bırak </string> + <string name="mam_prefs">Arşivleme tercihleri</string> + <string name="server_side_mam_prefs">Sunucu tarafı arşivleme tercihleri</string> + <string name="fetching_mam_prefs">Arşivleme tercihleri alınıyor. Lütfen bekleyin...</string> + <string name="unable_to_fetch_mam_prefs">Arşivleme tercihleri alınamadı</string> + <string name="captcha_ocr">Captcha metni</string> + <string name="captcha_required">Captcha gerekli</string> + <string name="captcha_hint">resimdeki metni girin</string> + <string name="certificate_chain_is_not_trusted">Sertifika zinciri güvenilir değil</string> + <string name="jid_does_not_match_certificate">Jabber ID sertifikayla eşleşmiyor</string> + <string name="action_renew_certificate">Sertifikayı yenile</string> + <string name="error_fetching_omemo_key">OMEMO anahtarı alınırken hata oluştu!</string> + <string name="verified_omemo_key_with_certificate">Sertifikalı OMEMO anahtarı onaylandı!</string> + <string name="device_does_not_support_certificates">Cihazınız seçilen istemci sertifikalarını desteklemiyor!</string> + <string name="pref_connection_options">Bağlantı</string> + <string name="account_settings_hostname">Sunucu adı</string> + <string name="account_settings_port">Port</string> + <string name="not_a_valid_port">Bu port numarası geçerli değil</string> + <string name="not_valid_hostname">Bu sunucu geçerli değil</string> + <string name="connected_accounts"> %2$d hesabın %1$ kadarı bağlandı</string> + <plurals name="x_messages"> + <item quantity="other">%d ileti</item> + </plurals> + <string name="shared_file_with_x">%s ile paylaşılan dosyalar</string> + <string name="shared_image_with_x">%s ile paylaşılan resim</string> + <string name="no_storage_permission">Conversations’ın harici depolama alanına erişmesi gerek </string> + <string name="sync_with_contacts">Kişilerle senkronize et</string> + <string name="sync_with_contacts_long">Conversations XMPP listenizi telefon rehberinizle eşleştirerek kişilerin tam isimlerini ve avatarlarını göstermek istiyor. \n\n Conversations telefon rehberinizi sadece okuyacak ve onları sunucunuza yüklemeden eşleştirecek. \n\n Şimdi telefon rehberinize erişilmesine izin vermeniz istenecek.\n\n</string> + <string name="certificate_information">Sertifika Bilgisi</string> + <string name="certificate_subject">Konu</string> + <string name="certificate_issuer">Veren</string> + <string name="certificate_cn">Ortak ad</string> + <string name="certificate_o">Organizasyon</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(mevcut değil)</string> + <string name="certificate_not_found">Sertifika bulunamadı</string> + <string name="notify_on_all_messages">Tüm iletilerde uyar</string> + <string name="notify_only_when_highlighted">Sadece işaretliyse uyar</string> + <string name="notify_never">Uyarılar devre dışı</string> + <string name="notify_paused">Uyarılar geçici olarak durduruldu</string> + <string name="always">Her zaman</string> + <string name="automatically">Otomatik olarak</string> + <string name="battery_optimizations_enabled">Pil optimizasyonu devrede</string> + <string name="battery_optimizations_enabled_explained">Cihazınız Conversations üzerinde yoğun pil optimizasyonu yaptığı için bildirimlerde gecikmeler olabilir hatta bazı ileti kayıpları yaşanabilir.\nBu durumla karşılaşamamak için devre dışı bırakmanız önerilir. </string> + <string name="battery_optimizations_enabled_dialog">Cihazınız Conversations üzerinde yoğun pil optimizasyonu yaptığı için bildirimlerde gecikmeler olabilir hatta bazı ileti kayıpları yaşanabilir.\n Şimdi bunları devre dışı bırakmanız istenecek.</string> + <string name="disable">Devre dışı</string> + <string name="selection_too_large">Seçilen alan çok büyük</string> + <string name="no_accounts">(Aktif hesap bulunmuyor)</string> + <string name="this_field_is_required">Bu alan zorunludur</string> +</resources> diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml new file mode 100644 index 00000000..c757504a --- /dev/null +++ b/src/main/res/values-uk/strings.xml @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='UTF-8'?> +<resources/> diff --git a/src/main/res/values-v21/themes.xml b/src/main/res/values-v21/themes.xml index d1679f92..91d43e77 100644 --- a/src/main/res/values-v21/themes.xml +++ b/src/main/res/values-v21/themes.xml @@ -2,10 +2,13 @@ <resources> <style name="ConversationsTheme" parent="@android:style/Theme.Material.Light.DarkActionBar"> - <item name="android:colorPrimary">@color/green500</item> - <item name="android:colorPrimaryDark">@color/green700</item> + <item name="android:colorPrimary">@color/primary</item> + <item name="android:colorPrimaryDark">@color/primary_dark</item> <item name="android:colorAccent">@color/accent</item> + <item name="android:windowActionModeOverlay">true</item> + <item name="android:actionModeBackground">@color/accent</item> + <item name="TextSizeInfo">12sp</item> <item name="TextSizeBody">14sp</item> <item name="TextSizeHeadline">20sp</item> @@ -18,8 +21,10 @@ <item name="attr/icon_download">@drawable/ic_file_download_white_24dp</item> <item name="attr/icon_edit">@drawable/ic_edit_white_24dp</item> <item name="attr/icon_edit_dark">@drawable/ic_edit_grey600_24dp</item> + <item name="attr/icon_done">@drawable/ic_done_black_24dp</item> <item name="attr/icon_group">@drawable/ic_group_white_24dp</item> <item name="attr/icon_new">@drawable/ic_add_white_24dp</item> + <item name="attr/icon_refresh">@drawable/ic_refresh_grey600_24dp</item> <item name="attr/icon_new_attachment">@drawable/ic_attach_file_white_24dp</item> <item name="attr/icon_not_secure">@drawable/ic_lock_open_white_24dp</item> <item name="attr/icon_remove">@drawable/ic_delete_grey600_24dp</item> diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml new file mode 100644 index 00000000..2635498e --- /dev/null +++ b/src/main/res/values-vi/strings.xml @@ -0,0 +1,534 @@ +<?xml version='1.0' encoding='UTF-8'?> +<resources> + <string name="action_settings">Cài đặt</string> + <string name="action_add">Hội thoại mới</string> + <string name="action_accounts">Quản lý tài khoản</string> + <string name="action_end_conversation">Kết thúc hội thoại</string> + <string name="action_contact_details">Thông tin liên hệ</string> + <string name="action_muc_details">Chi tiết diễn đàn</string> + <string name="action_secure">Bảo mật hội thoại</string> + <string name="action_add_account">Thêm tài khoản</string> + <string name="action_edit_contact">Chỉnh sửa tên</string> + <string name="action_add_phone_book">Thêm vào danh bạ</string> + <string name="action_delete_contact">Xoá khỏi danh sách bạn bè</string> + <string name="action_block_contact">Chặn liên hệ</string> + <string name="action_unblock_contact">Bỏ chặn liên hệ</string> + <string name="action_block_domain">Chặn miền</string> + <string name="action_unblock_domain">Bỏ chặn miền</string> + <string name="title_activity_manage_accounts">Quản lý tài khoản</string> + <string name="title_activity_settings">Cài đặt</string> + <string name="title_activity_conference_details">Chi tiết diễn đàn</string> + <string name="title_activity_contact_details">Chi tiết liên hệ</string> + <string name="title_activity_sharewith">Chia sẻ qua Conversation</string> + <string name="title_activity_start_conversation">Khởi chạy Conversation</string> + <string name="title_activity_choose_contact">Chọn liên hệ</string> + <string name="title_activity_block_list">Danh sách chặn</string> + <string name="just_now">mới đây</string> + <string name="minute_ago">1 phút trước</string> + <string name="minutes_ago">%d phút trước</string> + <string name="unread_conversations">Các hội thoại chưa đọc</string> + <string name="sending">đang gửi...</string> + <string name="message_decrypting">Đang giải mã tin nhắn. Xin chờ...</string> + <string name="pgp_message">Tin nhắn mã hoá bằng OpenPGP</string> + <string name="nick_in_use">Biệt danh đã được sử dụng</string> + <string name="admin">Quản trị viên</string> + <string name="owner">Chủ nhân</string> + <string name="moderator">Điều phối viên</string> + <string name="participant">Thành viên</string> + <string name="visitor">Khách</string> + <string name="remove_contact_text">Bạn có muốn xoá %s khỏi danh sách bạn bè? Hội thoại gắn với liên hệ này sẽ không bị xoá.</string> + <string name="block_contact_text">Bạn có muốn chặn %s gửi tin nhắn cho bạn?</string> + <string name="unblock_contact_text">Bạn có muốn bỏ chặn %s và cho phép họ gửi tin nhắn cho bạn?</string> + <string name="block_domain_text">Chặn tất cả liên hệ từ %s?</string> + <string name="unblock_domain_text">Bỏ chặn tất cả liên hệ từ %s?</string> + <string name="contact_blocked">Đã chặn liên hệ</string> + <string name="remove_bookmark_text">Bạn có muốn xoá đánh dấu %s? Hội thoại gắn với đánh dấu này sẽ không bị xoá.</string> + <string name="register_account">Đăng ký tài khoản mới trên máy chủ</string> + <string name="change_password_on_server">Đổi mật k trên máy chủ</string> + <string name="share_with">Chia sẻ với...</string> + <string name="start_conversation">Khởi chạy Conversation</string> + <string name="invite_contact">Mời liên hệ</string> + <string name="contacts">Danh bạ</string> + <string name="cancel">Huỷ</string> + <string name="set">Đặt</string> + <string name="add">Thêm</string> + <string name="edit">Chỉnh sửa</string> + <string name="delete">Xoá</string> + <string name="block">Chặn</string> + <string name="unblock">Bỏ chặn</string> + <string name="save">Lưu</string> + <string name="ok">OK</string> + <string name="crash_report_title">Conversations đã đột ngột dừng</string> + <string name="crash_report_message">Bằng việc gửi báo cáo hoạt động, bạn đang hỗ trợ nhóm phát triển của Conversations\n<b>Cảnh báo:</b> Thao tác này sẽ sử dụng tài khoản XMPP của bạn để gửi báo cáo hoạt động đến nhà phát triển.</string> + <string name="send_now">Gửi ngay</string> + <string name="send_never">Đừng hỏi lại nữa</string> + <string name="problem_connecting_to_account">Không thể kết nối đến tài khoản</string> + <string name="problem_connecting_to_accounts">Không thể kết nối đến nhiều tài khoản</string> + <string name="touch_to_fix">Chạm vào đây để quản lý các tài khoản</string> + <string name="attach_file">Đính kèm tập tin</string> + <string name="not_in_roster">Liên hệ này không có trong danh sách bạn bè. Bạn có muốn thêm nó vào?</string> + <string name="add_contact">Thêm liên hệ</string> + <string name="send_failed">thất bại khi chuyển</string> + <string name="send_rejected">bị từ chối</string> + <string name="preparing_image">Đang chuẩn bị hình để chuyển</string> + <string name="action_clear_history">Xoá lịch sử</string> + <string name="clear_conversation_history">Xoá lịch sử hội thoại</string> + <string name="clear_histor_msg">Bạn có muốn xoá toàn bộ tin nhắn trong hội thoại này?\n\n<b>Cảnh báo:</b> Thao tác này sẽ không ảnh hưởng đến tin nhắn đã lưu trên các thiết bị hoặc máy chủ khác.</string> + <string name="delete_messages">Xoá tin nhắn</string> + <string name="also_end_conversation">Dừng hội thoại này sau đó</string> + <string name="choose_presence">Chọn sự hiện diện đối với liên hệ</string> + <string name="send_unencrypted_message">Gửi tin nhắn không mã hoá</string> + <string name="send_otr_message">Gửi tin nhắn mã hoá OTR</string> + <string name="send_omemo_message">Gửi tin nhắn mã hoá OMEMO</string> + <string name="send_omemo_x509_message">Gửi tin nhắn mã hoá v\\OMEMO</string> + <string name="send_pgp_message">Gửi tin nhắn mã hoá OpenPGP</string> + <string name="your_nick_has_been_changed">Biệt danh của bạn đã được đổi</string> + <string name="send_unencrypted">Gửi dạng không mã hoá</string> + <string name="decryption_failed">Giải mã thất bại. Có lẽ bạn không có đúng khoá cá nhân.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations dùng ứng dụng bên thứ ba gọi là <b>OpenKeychain</b> để mã hoá và giải mã tin nhắn đồng thời quản lý khoá công cộng của bạn.\n\nOpenKeychain được cấp giấy phép GPLv3 và có thể tải về từ F-Droid cũng như Google Play.\n\n<small>(Xin khởi chạy lại Conversations sau đó.)</small></string> + <string name="restart">Khởi chạy lại</string> + <string name="install">Cài đặt</string> + <string name="openkeychain_not_installed">Xin cài đặt OpenKeychain</string> + <string name="offering">đang đề xuất...</string> + <string name="waiting">đang chờ...</string> + <string name="no_pgp_key">Không tìm thấy khoá OpenPGP</string> + <string name="contact_has_no_pgp_key">Conversations không thể mã hoá tin nhắn vì liên hệ của bạn không thông báo khoá công cộng của anh/chị ấy.\n\n<small>Hãy yêu cầu liên hệ đó cài đặt OpenPGP.</small></string> + <string name="no_pgp_keys">Không tìm thấy các khoá OpenPGP</string> + <string name="contacts_have_no_pgp_keys">Conversations không thể mã hoá tin nhắn vì các liên hệ của bạn không thông báo khoá công cộng của họ.\n\n<small>Hãy yêu cầu họ cài đặt OpenPGP.</small></string> + <string name="encrypted_message_received"><i>Đã nhận tin nhắn được mã hoá. Chạm để giải mã.</i></string> + <string name="pref_general">Tổng quan</string> + <string name="pref_xmpp_resource">Ứng dụng XMPP</string> + <string name="pref_xmpp_resource_summary">Tên của máy trạm này được tự đặt là</string> + <string name="pref_accept_files">Chấp thuận các tập tin</string> + <string name="pref_accept_files_summary">Tự động chấp thuận các tập tin nhỏ hơn...</string> + <string name="pref_notifications">Thông báo</string> + <string name="pref_notifications_summary">Thông báo khi có tin nhắn mới</string> + <string name="pref_vibrate">Rung</string> + <string name="pref_vibrate_summary">Rung khi có tin nhắn mới</string> + <string name="pref_sound">Âm báo</string> + <string name="pref_sound_summary">Chơi nhạc chuông cùng với thông báo</string> + <string name="pref_notification_grace_period">Thời gian gia hạn thông báo</string> + <string name="pref_notification_grace_period_summary">Tắt thông báo trong một thời gian ngắn sau khi nhận được bản sao giấy than</string> + <string name="pref_never_send_crash">Không bao giờ gửi báo cáo dừng chạy</string> + <string name="pref_never_send_crash_summary">Bằng việc gửi báo cáo hoạt động, bạn đang hỗ trợ nhóm phát triển của Conversations</string> + <string name="pref_confirm_messages">Xác nhận tin nhắn</string> + <string name="pref_confirm_messages_summary">Báo cho liên hệ của bạn biết khi bạn đã nhận và đọc tin nhắn</string> + <string name="openpgp_error">OpenKeychain đã báo cáo một lỗi</string> + <string name="error_decrypting_file">Tập tin giải mã lỗi I/O</string> + <string name="accept">Chấp thuận</string> + <string name="error">Đã có lỗi xảy ra</string> + <string name="pref_grant_presence_updates">Trao quyền cập nhật hiện diện</string> + <string name="pref_grant_presence_updates_summary">Ưu tiên trao quyền và hỏi đăng ký hiện diện cho các liên hệ bạn đã tạo</string> + <string name="subscriptions">Đăng ký</string> + <string name="your_account">Tài khoản của bạn</string> + <string name="keys">Các khoá</string> + <string name="send_presence_updates">Gửi cập nhật hiện diện</string> + <string name="receive_presence_updates">Nhận cập nhật hiện diện</string> + <string name="ask_for_presence_updates">Hỏi cập nhật hiện diện</string> + <string name="attach_choose_picture">Chọn hình</string> + <string name="attach_take_picture">Chụp hình</string> + <string name="preemptively_grant">Ưu tiên trao quyền yêu cầu đăng ký</string> + <string name="error_not_an_image_file">Tập tin bạn chọn không phải là hình ảnh</string> + <string name="error_compressing_image">Lỗi khi chuyển đổi tập tin hình ảnh</string> + <string name="error_file_not_found">Không tìm thấy tập tin</string> + <string name="error_io_exception">Lỗi I/O tổng quát. Có lẽ đã hết dung lượng lưu trữ?</string> + <string name="error_security_exception_during_image_copy">Ứng dụng mà bạn dùng để chọn hình này không cung cấp đủ quyền truy cập để đọc tập tin.\n\n<small>Hãy sử dụng trình quản lý tập tin khác để chọn hình</small></string> + <string name="account_status_unknown">Không rõ</string> + <string name="account_status_disabled">Tạm thời tắt</string> + <string name="account_status_online">Trực tuyến</string> + <string name="account_status_connecting">Đang kết nối\u2026</string> + <string name="account_status_offline">Ngoại tuyến</string> + <string name="account_status_unauthorized">Chưa xác minh</string> + <string name="account_status_not_found">Không tìm thấy máy chủ</string> + <string name="account_status_no_internet">Không có kết nối mạng</string> + <string name="account_status_regis_fail">Đăng ký thất bại</string> + <string name="account_status_regis_conflict">Tên người dùng đã được sử dụng</string> + <string name="account_status_regis_success">Đăng ký hoàn tất</string> + <string name="account_status_regis_not_sup">Máy chủ không hỗ trợ việc đăng ký</string> + <string name="account_status_security_error">Lỗi bảo mật</string> + <string name="account_status_incompatible_server">Máy chủ không tương thích</string> + <string name="encryption_choice_unencrypted">Không mã hoá</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Chỉnh sửa tài khoản</string> + <string name="mgmt_account_delete">Xoá tài khoản</string> + <string name="mgmt_account_disable">Tạm thời tắt</string> + <string name="mgmt_account_publish_avatar">Đăng ảnh đại diện</string> + <string name="mgmt_account_publish_pgp">Đăng khoá công cộng OpenPGP</string> + <string name="mgmt_account_enable">Bật tài khoản</string> + <string name="mgmt_account_are_you_sure">Bạn chắc chứ?</string> + <string name="mgmt_account_delete_confirm_text">Nếu xoá tài khoản, toàn bộ lịch sử hội thoại của bạn sẽ bị mất</string> + <string name="attach_record_voice">Ghi âm</string> + <string name="account_settings_jabber_id">ID Jabber</string> + <string name="account_settings_password">Mật khẩu</string> + <string name="account_settings_example_jabber_id">username@example.com</string> + <string name="account_settings_confirm_password">Xác nhận mật khẩu</string> + <string name="password">Mật khẩu</string> + <string name="confirm_password">Xác nhận mật khẩu</string> + <string name="passwords_do_not_match">Mật khẩu không trùng khớp</string> + <string name="invalid_jid">ID Jabber này không hợp lệ</string> + <string name="error_out_of_memory">Hết bộ nhớ. Ảnh quá lớn</string> + <string name="add_phone_book_text">Bạn có muốn thêm %s vào danh bạ?</string> + <string name="contact_status_online">trực tuyến</string> + <string name="contact_status_free_to_chat">rảnh để trò chuyện</string> + <string name="contact_status_away">vắng mặt</string> + <string name="contact_status_extended_away">vắng mặt mở rộng</string> + <string name="contact_status_do_not_disturb">đừng làm phiền</string> + <string name="contact_status_offline">ngoại tuyến</string> + <string name="muc_details_conference">Diễn đàn</string> + <string name="muc_details_other_members">Các thành viên khác</string> + <string name="server_info_show_more">Thông tin máy chủ</string> + <string name="server_info_mam">XEP-0313: MAM</string> + <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> + <string name="server_info_csi">XEP-0352: Biểu thị trạng thái máy trạm</string> + <string name="server_info_blocking">XEP-0191: Blocking Command</string> + <string name="server_info_roster_version">XEP-0237: Phiên bản hoá danh sách bạn bè</string> + <string name="server_info_stream_management">XEP-0198: Stream Management</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> + <string name="server_info_available">sẵn sàng</string> + <string name="server_info_unavailable">không sẵn sàng</string> + <string name="missing_public_keys">Thông báo khoá công cộng bị thất lạc</string> + <string name="last_seen_now">thấy lần cuối vừa đây</string> + <string name="last_seen_min">thấy lần cuối 1 phút trước</string> + <string name="last_seen_mins">thấy lần cuối %d phút trước</string> + <string name="last_seen_hour">thấy lần cuối 1 tiếng trước</string> + <string name="last_seen_hours">thấy lần cuối %d tiếng trước</string> + <string name="last_seen_day">thấy lần cuối 1 ngày trước</string> + <string name="last_seen_days">thấy lần cuối %d ngày trước</string> + <string name="never_seen">chưa từng gặp</string> + <string name="install_openkeychain">Tin nhắn được mã hoá. Xin cài đặt OpenKeychain để giải mã.</string> + <string name="unknown_otr_fingerprint">Dấu vân tay OTR lạ</string> + <string name="openpgp_messages_found">Đã tìm thấy các tin nhắn mã hoá OpenPGP</string> + <string name="reception_failed">Tiếp nhận thất bại</string> + <string name="your_fingerprint">Dấu vân tay của bạn</string> + <string name="otr_fingerprint">Dấu vân tay OTR</string> + <string name="omemo_fingerprint">Dấu vân tay OMEMO</string> + <string name="omemo_fingerprint_x509">Dấu vân tay v\\OMEMO</string> + <string name="omemo_fingerprint_selected_message">Dấu vân tay OMEMO của tin nhắn</string> + <string name="omemo_fingerprint_x509_selected_message">Dấu vân tay v\\OMEMO của tin nhắn</string> + <string name="this_device_omemo_fingerprint">Dấu vân tay OMEMO thiết bị</string> + <string name="other_devices">Các thiết bị khác</string> + <string name="trust_omemo_fingerprints">Tin tưởng các dấu vân tay OMEMO</string> + <string name="fetching_keys">Đang nhận khoá...</string> + <string name="done">Xong</string> + <string name="verify">Xác minh</string> + <string name="decrypt">Giải mã</string> + <string name="conferences">Các diễn đàn</string> + <string name="search">Tìm kiếm</string> + <string name="create_contact">Tạo liên hệ</string> + <string name="enter_contact">Nhập liên hệ</string> + <string name="join_conference">Tham gia diễn đàn</string> + <string name="delete_contact">Xoá liên hệ</string> + <string name="view_contact_details">Xem chi tiết liên hệ</string> + <string name="block_contact">Chặn liên hệ</string> + <string name="unblock_contact">Bỏ chặn liên hệ</string> + <string name="create">Tạo</string> + <string name="select">Chọn</string> + <string name="contact_already_exists">Đã có liên hệ này rồi</string> + <string name="join">Tham gia</string> + <string name="conference_address">Địa chỉ diễn đàn</string> + <string name="conference_address_example">room@conference.example.com</string> + <string name="save_as_bookmark">Lưu thành đánh dấu</string> + <string name="delete_bookmark">Xoá đánh dấu</string> + <string name="bookmark_already_exists">Đã có đánh dấu này rồi</string> + <string name="you">Bạn</string> + <string name="action_edit_subject">Chỉnh sửa tiêu đề diễn đàn</string> + <string name="conference_not_found">Không tìm thấy diễn đàn</string> + <string name="leave">Rời khỏi</string> + <string name="contact_added_you">Liên hệ đã thêm bạn vào danh bạ</string> + <string name="add_back">Thêm họ vào</string> + <string name="contact_has_read_up_to_this_point">%s đã đọc đến điểm này</string> + <string name="publish">Đăng</string> + <string name="touch_to_choose_picture">Chạm ảnh đại diện để chọn hình từ bộ sưu tập</string> + <string name="publish_avatar_explanation">Xin chú ý: Ai đã đăng ký nhận cập nhật hiện diện của bạn sẽ có quyền xem hình này.</string> + <string name="publishing">Đang đăng...</string> + <string name="error_publish_avatar_server_reject">Máy chủ đã từ chối đăng tải của bạn</string> + <string name="error_publish_avatar_converting">Đã xảy ra lỗi khi chuyển đổi hình của bạn</string> + <string name="error_saving_avatar">Không thể lưu ảnh đại diện vào ổ đĩa</string> + <string name="or_long_press_for_default">(Hoặc nhấn giữ để chuyển về mặc định)</string> + <string name="error_publish_avatar_no_server_support">Máy chủ của bạn không hỗ trợ việc đăng tải ảnh đại diện</string> + <string name="private_message">đã thì thầm</string> + <string name="private_message_to">đến %s</string> + <string name="send_private_message_to">Gửi tin nhắn riêng tư đến %s</string> + <string name="connect">Kết nối</string> + <string name="account_already_exists">Đã có tài khoản này rồi</string> + <string name="next">Tiếp theo</string> + <string name="server_info_session_established">Đã thiết lập phiên hiện thời</string> + <string name="additional_information">Thông tin thêm</string> + <string name="skip">Bỏ qua</string> + <string name="disable_notifications">Tắt thông báo</string> + <string name="disable_notifications_for_this_conversation">Tắt thông báo cho hội thoại này</string> + <string name="enable">Bật</string> + <string name="conference_requires_password">Diễn đàn yêu cầu mật khẩu</string> + <string name="enter_password">Nhập mật khẩu</string> + <string name="missing_presence_updates">Đang mất cập nhật hiện diện từ liên hệ</string> + <string name="request_presence_updates">Xin yêu cầu cập nhật hiện diện từ liên hệ của bạn trước.\n\n<small>Nó sẽ được dùng để xác định (các) liên hệ hiện đang sử dụng máy trạm nào.</small></string> + <string name="request_now">Yêu cầu ngay</string> + <string name="delete_fingerprint">Xoá dấu vân tay</string> + <string name="sure_delete_fingerprint">Có chắc là bạn muốn xoá dấu vân tay này?</string> + <string name="ignore">Bỏ qua</string> + <string name="without_mutual_presence_updates"><b>Cảnh báo:</b> Gửi mà không có cập nhật hiện diện của nhau có thể gây ra vấn đề khó lường.\n\n<small>Vào chi tiết liên hệ để xác minh đăng ký hiện diện của bạn.</small></string> + <string name="pref_force_encryption">Bắt buộc mã hoá hai đầu</string> + <string name="pref_force_encryption_summary">Luôn gửi tin nhắn được mã hoá (ngoại trừ trong diễn đàn)</string> + <string name="pref_dont_save_encrypted">Đừng lưu tin nhắn được mã hoá</string> + <string name="pref_dont_save_encrypted_summary">Cảnh báo: Có thể làm mất tin nhắn</string> + <string name="pref_expert_options_summary">Xin hãy cẩn trọng với chúng</string> + <string name="title_activity_about">Thông tin về Conversations</string> + <string name="pref_about_conversations_summary">Thông tin bản dựng và giấy phép</string> + <string name="title_pref_quiet_hours">Giờ yên lặng</string> + <string name="title_pref_quiet_hours_start_time">Thời gian bắt đầu</string> + <string name="title_pref_quiet_hours_end_time">Thời gian kết thúc</string> + <string name="title_pref_enable_quiet_hours">Bật giờ yên lặng</string> + <string name="pref_quiet_hours_summary">Thông báo sẽ được tắt trong giờ yên lặng</string> + <string name="pref_use_larger_font">Tăng kích cỡ chữ</string> + <string name="pref_use_larger_font_summary">Dùng cỡ chữ lớn hơn trên toàn ứng dụng</string> + <string name="pref_use_send_button_to_indicate_status">Nút gửi biểu thị trạng thái</string> + <string name="pref_use_indicate_received">Yêu cầu thông báo nhận tin nhắn</string> + <string name="pref_use_indicate_received_summary">Các tin nhắn đã nhận sẽ được đánh dấu kiểm màu xanh lá nếu được hỗ trợ</string> + <string name="pref_use_send_button_to_indicate_status_summary">Tô màu nút gửi để biểu thị trạng thái của liên hệ</string> + <string name="pref_expert_options_other">Khác</string> + <string name="pref_conference_name">Tên diễn đàn</string> + <string name="pref_conference_name_summary">Dùng tiêu đề của phòng thay cho JID để định danh diễn đàn</string> + <string name="toast_message_otr_fingerprint">Đã chép dấu vân tay OTR vào clipboard!</string> + <string name="toast_message_omemo_fingerprint">Đã chép dấu vân tay OMEMO vào clipboard!</string> + <string name="conference_banned">Bạn đã bị cấm tham gia diễn đàn này</string> + <string name="conference_members_only">Diễn đàn này chỉ dành cho thành viên</string> + <string name="conference_kicked">Bạn đã bị đuổi khỏi diễn đàn này</string> + <string name="using_account">đang dùng tài khoản %s</string> + <string name="checking_x">Đang kiểm tra %s trên máy chủ HTTTP</string> + <string name="not_connected_try_again">Bạn chưa kết nối mạng. Xin thử lại sau</string> + <string name="check_x_filesize">Kiểm tra kích cỡ %s</string> + <string name="message_options">Tuỳ chọn tin nhắn</string> + <string name="copy_text">Sao chép văn bản</string> + <string name="copy_original_url">Sao chép URL gốc</string> + <string name="send_again">Gửi lại</string> + <string name="file_url">URL tập tin</string> + <string name="message_text">Văn bản tin nhắn</string> + <string name="url_copied_to_clipboard">Đã chép URL vào clipboard</string> + <string name="message_copied_to_clipboard">Đã chép tin nhắn vào clipboard</string> + <string name="image_transmission_failed">Thất bại khi chuyển hình</string> + <string name="scan_qr_code">Quét mã QR</string> + <string name="show_qr_code">Hiện mã QR</string> + <string name="show_block_list">Quét danh sách chặn</string> + <string name="account_details">Chi tiết tài khoản</string> + <string name="verify_otr">Xác minh OTR</string> + <string name="remote_fingerprint">Dấu vân tay từ xa</string> + <string name="scan">quét</string> + <string name="smp">Giao thức Socialist Millionaire</string> + <string name="shared_secret_hint">Gợi ý hoặc Câu hỏi</string> + <string name="shared_secret_secret">Bí mật đã chia sẻ</string> + <string name="confirm">Xác nhận</string> + <string name="in_progress">Đang diễn ra</string> + <string name="respond">Hồi đáp</string> + <string name="failed">Đã thất bại</string> + <string name="secrets_do_not_match">Bí mật không trùng khớp</string> + <string name="try_again">Thử lại</string> + <string name="finish">Kết thúc</string> + <string name="verified">Đã xác minh!</string> + <string name="smp_requested">Liên hệ đã yêu cầu xác minh SMP</string> + <string name="no_otr_session_found">Không tìm thấy phiên OTR hợp lệ nào!</string> + <string name="conversations_foreground_service">Conversations</string> + <string name="pref_keep_foreground_service">Giữ dịch vụ luôn chạy</string> + <string name="pref_keep_foreground_service_summary">Ngăn hệ điều hành ngắt kết nối của bạn</string> + <string name="pref_export_logs">Xuất nhật ký</string> + <string name="pref_export_logs_summary">Chép nhật ký vào thẻ SD</string> + <string name="notification_export_logs_title">Đang chép nhật ký vào thẻ SD</string> + <string name="choose_file">Chọn tập tin</string> + <string name="receiving_x_file">Đang nhận %1$s (đã hoàn tất %2$d%%)</string> + <string name="download_x_file">Tải về %s</string> + <string name="file">tập tin</string> + <string name="open_x_file">Mở %s</string> + <string name="sending_file">đang gửi (đã hoàn tất %1$d%%)</string> + <string name="preparing_file">Đang chuẩn bị tập tin để chuyển</string> + <string name="x_file_offered_for_download">Đã đề xuất tải về %s</string> + <string name="cancel_transmission">Huỷ chuyển tập tin</string> + <string name="file_transmission_failed">chuyển tập tin đã thất bại</string> + <string name="file_deleted">Đã xoá tập tin</string> + <string name="no_application_found_to_open_file">Không tìm thấy ứng dụng nào để mở tập tin</string> + <string name="could_not_verify_fingerprint">Không thể xác minh dấu vân tay</string> + <string name="manually_verify">Xác minh thủ công</string> + <string name="are_you_sure_verify_fingerprint">Có chắc là bạn muốn xác minh dấu vân tay OTR của liên hệ không?</string> + <string name="pref_show_dynamic_tags">Hiện các nhãn động</string> + <string name="pref_show_dynamic_tags_summary">Hiện nhãn chỉ đọc bên dưới các liên hệ</string> + <string name="enable_notifications">Bật thông báo</string> + <string name="conference_with">Tạo diễn đàn với...</string> + <string name="no_conference_server_found">Không tìm thấy máy chủ diễn đàn nào</string> + <string name="conference_creation_failed">Tạo diễn đàn thất bại!</string> + <string name="conference_created">Đã tạo diễn đàn!</string> + <string name="secret_accepted">Đã chấp thuận bí mật!</string> + <string name="reset">Cài lại</string> + <string name="account_image_description">Ảnh đại diện tài khoản</string> + <string name="copy_otr_clipboard_description">Sao chép dấu vân tay OTR vào clipboard</string> + <string name="copy_omemo_clipboard_description">Sao chép dấu vân tay OMEMO vào clipboard</string> + <string name="regenerate_omemo_key">Tạo lại khoá OMEMO</string> + <string name="wipe_omemo_pep">Xoá các thiết bị khác khỏi PEP</string> + <string name="clear_other_devices">Xoá các thiết bị</string> + <string name="clear_other_devices_desc">Có chắc là bạn muốn xoá toàn bộ các thiết bị khác khỏi thông báo OMEMO? Lần sau khi kết nối, các thiết bị sẽ tự thông báo lại nhưng có khả năng chúng sẽ không nhận được tin nhắn đã gửi trong khoảng thời gian đó.</string> + <string name="purge_key">Xoá khoá</string> + <string name="purge_key_desc_part1">Có chắc là bạn muốn xoá khoá này không?</string> + <string name="purge_key_desc_part2">Nó sẽ bị xem là hỏng mãi mãi và bạn sẽ không thể dựng một phiên khác với nó nữa.</string> + <string name="error_no_keys_to_trust_server_error">Không có khoá nào dùng được cho liên hệ này.\nViệc nhận khoá mới từ máy chủ cũng thất bại. Có lẽ là máy chủ dành cho các liên hệ đã gặp sự cố gì đó.</string> + <string name="error_no_keys_to_trust">Không còn khoá nào sẵn có cho liên hệ này. Nếu bạn đã xoá bất kỳ khoá nào, họ phải tạo khoá mới.</string> + <string name="error_trustkeys_title">Lỗi</string> + <string name="fetching_history_from_server">Đang nhận lịch sử từ máy chủ</string> + <string name="no_more_history_on_server">Không còn lịch sử nào trên máy chủ</string> + <string name="updating">Đang cập nhật...</string> + <string name="password_changed">Đã đổi mật khẩu!</string> + <string name="could_not_change_password">Không thể đổi mật khẩu</string> + <string name="otr_session_not_started">Gửi tin nhắn để bắt đầu chat có mã hoá</string> + <string name="ask_question">Đưa câu hỏi</string> + <string name="smp_explain_question">Nếu bạn và liên hệ có một bí mật chung mà không ai khác biết (như là một câu nói đùa hoặc chỉ đơn giản là bữa trưa trong lần gặp gần đây nhất), bạn có thể dùng bí mật đó để xác minh dấu vân tay của nhau.\n\nBạn đưa ra gợi ý hoặc câu hỏi cho liên hệ và họ sẽ phải nhập câu trả lời có tính cả viết hoa viết thường.</string> + <string name="smp_explain_answer">Liên hệ muốn xác minh dấu vân tay của bạn bằng một bí mật đã chia sẻ. Liên hệ đã cung cấp gợi ý hoặc câu hỏi cho bí mật sau đây.</string> + <string name="shared_secret_hint_should_not_be_empty">Không được để trống gợi ý</string> + <string name="shared_secret_can_not_be_empty">Không được để trống bí mật đã chia sẻ</string> + <string name="manual_verification_explanation">So sánh cẩn thận dấu vân tay dưới đây với dấu của liên hệ.\nBạn có thể dùng bất kỳ phương tiện giao tiếp đáng tin cậy nào như email được mã hoá hoặc điện thoại để trao đổi vân tay.</string> + <string name="change_password">Đổi mật khẩu</string> + <string name="current_password">Mật khẩu hiện tại</string> + <string name="new_password">Mật khẩu mới</string> + <string name="password_should_not_be_empty">Không được để trống mật khẩu</string> + <string name="enable_all_accounts">Bật toàn bộ tài khoản</string> + <string name="disable_all_accounts">Tắt toàn bộ tài khoản</string> + <string name="perform_action_with">Thực hiện thao tác với</string> + <string name="no_affiliation">Không có quan hệ gì</string> + <string name="no_role">Không có phận sự gì</string> + <string name="outcast">Kẻ bị ruồng bỏ</string> + <string name="member">Thành viên</string> + <string name="advanced_mode">Chế độ nâng cao</string> + <string name="grant_membership">Trao quyền thành viên</string> + <string name="remove_membership">Huỷ quyền thành viên</string> + <string name="grant_admin_privileges">Trao quyền quản trị</string> + <string name="remove_admin_privileges">Huỷ quyền quản trị</string> + <string name="remove_from_room">Xoá khỏi diễn đàn</string> + <string name="could_not_change_affiliation">Không thể đổi mối quan hệ của %s</string> + <string name="ban_from_conference">Cấm khỏi diễn đàn</string> + <string name="removing_from_public_conference">Bạn đang cố loại bỏ %s ra khỏi một diễn đàn công cộng. Biện pháp duy nhất là cấm người dùng đó mãi mãi.</string> + <string name="ban_now">Cấm ngay</string> + <string name="could_not_change_role">Không thể đổi phận sự của %s</string> + <string name="public_conference">Diễn đàn công cộng</string> + <string name="private_conference">Diễn đàn riêng, chỉ dành cho thành viên</string> + <string name="conference_options">Tuỳ chọn diễn đàn</string> + <string name="members_only">Riêng, chỉ dành cho thành viên</string> + <string name="non_anonymous">Không ẩn danh</string> + <string name="moderated">Đã điều phối</string> + <string name="you_are_not_participating">Hiện bạn chưa tham gia</string> + <string name="modified_conference_options">Tuỳ chọn diễn đàn đã chỉnh sửa!</string> + <string name="could_not_modify_conference_options">Không thể chỉnh sửa tuỳ chọn diễn đàn</string> + <string name="never">Chưa từng</string> + <string name="thirty_minutes">30 phút</string> + <string name="one_hour">1 tiếng</string> + <string name="two_hours">2 tiếng</string> + <string name="eight_hours">8 tiếng</string> + <string name="until_further_notice">Cho đến thông báo tiếp theo</string> + <string name="pref_enter_is_send">Bấm Enter để gửi</string> + <string name="pref_enter_is_send_summary">Bấm nút Enter để gửi tin nhắn</string> + <string name="pref_display_enter_key">Hiện nút Enter</string> + <string name="pref_display_enter_key_summary">Đổi nút biểu tượng cảm xúc thành nút Enter</string> + <string name="audio">âm thanh</string> + <string name="video">video</string> + <string name="image">hình ảnh</string> + <string name="pdf_document">tài liệu PDF</string> + <string name="apk">Ứng dụng Android</string> + <string name="vcard">Liên hệ</string> + <string name="received_x_file">Đã nhận %s</string> + <string name="disable_foreground_service">Tắt dịch vụ luôn chạy</string> + <string name="touch_to_open_conversations">Chạm để mở Conversations</string> + <string name="avatar_has_been_published">Đã đăng tải ảnh đại diện!</string> + <string name="sending_x_file">Đang gửi %s</string> + <string name="offering_x_file">Đang đề xuất %s</string> + <string name="hide_offline">Ẩn ngoại tuyến</string> + <string name="disable_account">Tắt tài khoản</string> + <string name="contact_is_typing">%s đang gõ...</string> + <string name="contact_has_stopped_typing">%s đã ngừng gõ</string> + <string name="pref_chat_states">Thông báo đang gõ</string> + <string name="pref_chat_states_summary">Báo cho liên hệ biết khi bạn đang viết tin nhắn mới</string> + <string name="send_location">Gửi vị trí</string> + <string name="show_location">Hiện vị trí</string> + <string name="no_application_found_to_display_location">Không thấy ứng dụng nào có thể hiện vị trí</string> + <string name="location">Vị trí</string> + <string name="received_location">Vị trí đã nhận</string> + <string name="title_undo_swipe_out_conversation">Đã đóng cuộc hội thoại</string> + <string name="title_undo_swipe_out_muc">Đã rời khỏi diễn đàn</string> + <string name="pref_dont_trust_system_cas_title">Đừng tin các CA hệ thống</string> + <string name="pref_dont_trust_system_cas_summary">Tất cả chứng nhận phải được phê duyệt thủ công</string> + <string name="pref_remove_trusted_certificates_title">Xoá các chứng nhận</string> + <string name="pref_remove_trusted_certificates_summary">Xoá thủ công các chứng nhận đã phê duyệt</string> + <string name="toast_no_trusted_certs">Không có chứng nhận được phê duyệt thủ công</string> + <string name="dialog_manage_certs_title">Xoá các chứng nhận</string> + <string name="dialog_manage_certs_positivebutton">Xoá lựa chọn</string> + <string name="dialog_manage_certs_negativebutton">Huỷ</string> + <plurals name="toast_delete_certificates"> + <item quantity="other">Đã xoá %d chứng nhận</item> + </plurals> + <plurals name="select_contact"> + <item quantity="other">Chọn %d liên hệ</item> + </plurals> + <string name="pref_quick_action_summary">Thay nút gửi với thao tác nhanh</string> + <string name="pref_quick_action">Thao tác nhanh</string> + <string name="none">Không có</string> + <string name="recently_used">Dùng gần đây nhất</string> + <string name="choose_quick_action">Chọn thao tác nhanh</string> + <string name="search_for_contacts_or_groups">Tìm các liên hệ và nhóm</string> + <string name="send_private_message">Gửi tin nhắn cá nhân</string> + <string name="user_has_left_conference">%s đã rời khỏi diễn đàn!</string> + <string name="username">Tên người dùng</string> + <string name="username_hint">Tên người dùng</string> + <string name="invalid_username">Đây không phải là tên người dùng hợp lệ</string> + <string name="download_failed_server_not_found">Tải xuống thất bại: Không thấy máy chủ</string> + <string name="download_failed_file_not_found">Tải xuống thất bại: Không thấy tập tin</string> + <string name="download_failed_could_not_connect">Tải xuống thất bại: Không thể kết nối đến máy chủ</string> + <string name="pref_use_white_background">Dùng nền trắng</string> + <string name="pref_use_white_background_summary">Hiện các tin nhắn nhận được dưới dạng chữ đen trên nền trắng</string> + <string name="account_status_tor_unavailable">Mạng Tor chưa sẵn sàng</string> + <string name="server_info_broken">Bị hỏng</string> + <string name="pref_away_when_screen_off">Vắng mặt khi màn hình tắt</string> + <string name="pref_away_when_screen_off_summary">Hiện ứng dụng là \'vắng mặt\' khi màn hình tắt</string> + <string name="pref_xa_on_silent_mode">Không sẵn sàng trong chế độ yên lặng</string> + <string name="pref_xa_on_silent_mode_summary">Hiện ứng dụng là \'không sẵn sàng\' khi thiết bị ở chế độ yên lặng</string> + <string name="action_add_account_with_certificate">Thêm tài khoản với chứng nhận</string> + <string name="unable_to_parse_certificate">Không thể phân tích chứng nhận</string> + <string name="authenticate_with_certificate">Bỏ trống để xác minh với chứng nhận</string> + <string name="captcha_ocr">Các ký tự xác minh</string> + <string name="captcha_required">Yêu cầu hình xác minh</string> + <string name="captcha_hint">nhập văn bản từ hình</string> + <string name="certificate_chain_is_not_trusted">Chuỗi chứng nhận không được tin tưởng</string> + <string name="jid_does_not_match_certificate">ID Jabber không khớp với chứng nhận</string> + <string name="action_renew_certificate">Gia hạn chứng nhận</string> + <string name="error_fetching_omemo_key">Lỗi nhập khoá OMEMO!</string> + <string name="verified_omemo_key_with_certificate">Khoá OMEMO đã xác minh với chứng nhận!</string> + <string name="device_does_not_support_certificates">Thiết bị không hỗ trợ chọn lựa các chứng chỉ của máy trạm!</string> + <string name="account_settings_hostname">Tên máy chủ</string> + <string name="account_settings_port">Cổng</string> + <string name="not_a_valid_port">Đây không phải là số cổng hợp lệ</string> + <string name="not_valid_hostname">Đây không phải là tên máy chủ hợp lệ</string> + <string name="connected_accounts">%1$d trên %2$d tài khoản đã kết nối</string> + <plurals name="x_messages"> + <item quantity="other">%dv tin nhắn</item> + </plurals> + <string name="shared_file_with_x">Chia sẻ tập tin với %s</string> + <string name="shared_image_with_x">Chia sẻ hình với %s</string> + <string name="no_storage_permission">Conversations cần quyền truy cập ổ nhớ ngoài</string> + <string name="sync_with_contacts">Đồng bộ với danh bạ</string> + <string name="sync_with_contacts_long">Conversations muốn so sánh danh sách bạn bè XMPP với danh bạ của bạn để hiện tên đầy đủ và ảnh đại diện của họ.\n\nConversations sẽ chỉ đọc danh bạ và so sánh trên thiết bị chứ không tải lên máy chủ.\n\nBây giờ, bạn sẽ được hỏi trao quyền truy cập danh bạ.</string> + <string name="certificate_information">Thông tin chứng nhận</string> + <string name="certificate_subject">Tiêu đề</string> + <string name="certificate_issuer">Nhà phát hành</string> + <string name="certificate_cn">Tên thường</string> + <string name="certificate_o">Tổ chức</string> + <string name="certicate_info_not_available">(Không sẵn sàng)</string> + <string name="certificate_not_found">Không thấy chứng nhận nào</string> + <string name="notify_on_all_messages">Thông báo tất cả tin nhắn</string> + <string name="notify_only_when_highlighted">Thông báo chỉ khi được làm nổi bật</string> + <string name="notify_never">Đã tắt thông báo</string> + <string name="notify_paused">Đã dừng thông báo</string> + <string name="pref_picture_compression">Nén hình ảnh</string> + <string name="always">Luôn luôn</string> + <string name="automatically">Tự động</string> + <string name="battery_optimizations_enabled">Đã bật tối ưu pin</string> + <string name="battery_optimizations_enabled_explained">Thiết bị đang thực hiện tối ưu hoá pin trên Conversations và nó có thể gây trì hoãn thông báo hay thậm chí làm mất tin nhắn.\nBạn nên tắt chế độ đó đi.</string> + <string name="battery_optimizations_enabled_dialog">Thiết bị đang thực hiện tối ưu hoá pin trên Conversations và nó có thể gây trì hoãn thông báo hay thậm chí làm mất tin nhắn.\n\nBây giờ, bạn sẽ được yêu cầu tắt chế độ đó đi.</string> + <string name="disable">Tắt</string> + <string name="selection_too_large">Khu vực chọn quá lớn</string> +</resources> diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 32bfa38a..4f3c79ff 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -9,7 +9,7 @@ <string name="action_secure">安全对话</string> <string name="action_add_account">添加账号</string> <string name="action_edit_contact">编辑姓名</string> - <string name="action_add_phone_book">添加到手机通讯录</string> + <string name="action_add_phone_book">添加到地址薄</string> <string name="action_delete_contact">从列表中删除</string> <string name="action_block_contact">屏蔽联系人</string> <string name="action_unblock_contact">解除联系人屏蔽</string> @@ -28,7 +28,8 @@ <string name="minutes_ago">%d分钟前</string> <string name="unread_conversations">未读会话</string> <string name="sending">正在发送…</string> - <string name="encrypted_message">解密信息中. 请稍候…</string> + <string name="message_decrypting">解密信息中. 请稍候…</string> + <string name="pgp_message">OpenPGP 加密的信息</string> <string name="nick_in_use">该名称已存在</string> <string name="admin">管理员</string> <string name="owner">所有者</string> @@ -74,47 +75,48 @@ <string name="clear_conversation_history">清除会话记录</string> <string name="clear_histor_msg">删除该会话中所有信息?\n\n<b>注:</b> 该操作不会影响其他设备或服务器保存的信息。</string> <string name="delete_messages">删除消息</string> - <string name="also_end_conversation">之后结束该会话</string> + <string name="also_end_conversation">结束此会话以后</string> <string name="choose_presence">添加在线用户至联系人</string> - <string name="send_plain_text_message">发送纯文本信息</string> + <string name="send_unencrypted_message">发送未加密的信息</string> <string name="send_otr_message">发送 OTR 加密信息</string> + <string name="send_omemo_message">发送 OMEMO 加密信息</string> + <string name="send_omemo_x509_message">发送 v\\OMEMO 加密信息</string> <string name="send_pgp_message">发送 OpenPGP 加密信息</string> <string name="your_nick_has_been_changed">昵称修改成功</string> <string name="send_unencrypted">不加密发送</string> <string name="decryption_failed">解密失败,可能是私钥不正确。</string> <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">会话运用了第三方app,名为 <b>OpenKeychain</b> 用来加密、解密信息以及管理您的密钥。\n\nOpenKeychain 遵循 GPLv3 并且可以在 F-Droid 和 Google Play 上获取。\n\n<small>(之后请重启 conversations)</small></string> + <string name="openkeychain_required_long">Conversations 使用了第三方app <b>OpenKeychain</b> 来加密、解密信息并管理您的密钥。\n\nOpenKeychain 遵循 GPLv3 并且可以在 F-Droid 和 Google Play 上获取。\n\n<small>(之后请重启 Conversations)</small></string> <string name="restart">重启</string> <string name="install">安装</string> + <string name="openkeychain_not_installed">请安装 OpenKeychain 以解密</string> <string name="offering">输入…</string> <string name="waiting">等待…</string> <string name="no_pgp_key">未发现 OpenPGP 密钥</string> - <string name="contact_has_no_pgp_key">会话加密信息失败,因为联系人未提供他/她的公钥。\n\n<small>请通知联系人设置 OpenPGP。</small></string> + <string name="contact_has_no_pgp_key">Conversations 无法加密信息,因为联系人未提供他/她的公钥。\n\n<small>请通知联系人设置 OpenPGP。</small></string> <string name="no_pgp_keys">未找到 OpenPGP 密钥</string> <string name="contacts_have_no_pgp_keys">因您的联系人未公布公钥,Conversations未能成功加密您的信息.\n\n<small>请通知联系人设置OpenPGP.</small></string> - <string name="encrypted_message_received"><i>加密信息已接收。点击解密并查看。</i></string> + <string name="encrypted_message_received"><i>接收到加密消息。轻触以解密。</i></string> <string name="pref_general">常规</string> <string name="pref_xmpp_resource">XMPP 资源</string> <string name="pref_xmpp_resource_summary">客户端标识名称</string> <string name="pref_accept_files">接收文件</string> - <string name="pref_accept_files_size_summary">自动接收小于 … 的文件</string> - <string name="pref_notification_settings">通知设置</string> + <string name="pref_accept_files_summary">自动接收小于 … 的文件</string> + <string name="pref_notification_settings">通知</string> <string name="pref_notifications">通知</string> <string name="pref_notifications_summary">收到新消息时通知</string> <string name="pref_vibrate">震动</string> <string name="pref_vibrate_summary">收到新消息时震动</string> <string name="pref_sound">声音</string> <string name="pref_sound_summary">收到新消息时的铃声</string> - <string name="pref_conference_notifications">讨论组通知</string> - <string name="pref_conference_notifications_summary">当有新的消息时总是通知而不是亮屏时才通知</string> <string name="pref_notification_grace_period">通知限期</string> <string name="pref_notification_grace_period_summary">接收副本短时间内关闭通知</string> - <string name="pref_advanced_options">高级选项</string> + <string name="pref_advanced_options">高级</string> <string name="pref_never_send_crash">总不发送崩溃报告</string> <string name="pref_never_send_crash_summary">发送堆栈跟踪帮助 Conversations 开发人员</string> <string name="pref_confirm_messages">确认消息</string> <string name="pref_confirm_messages_summary">当你已收到消息并且已阅时通知好友</string> - <string name="pref_ui_options">UI 选项</string> + <string name="pref_ui_options">UI</string> <string name="openpgp_error">OpenKeychain 报告了一个错误</string> <string name="error_decrypting_file">解密文件时出现 I/O 错误</string> <string name="accept">接受</string> @@ -149,9 +151,10 @@ <string name="account_status_regis_not_sup">服务器不支持注册</string> <string name="account_status_security_error">安全错误</string> <string name="account_status_incompatible_server">服务器不兼容</string> - <string name="encryption_choice_none">纯文本内容</string> + <string name="encryption_choice_unencrypted">未加密</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> <string name="mgmt_account_edit">编辑账号</string> <string name="mgmt_account_delete">删除账号</string> <string name="mgmt_account_disable">暂时不可用</string> @@ -170,7 +173,7 @@ <string name="passwords_do_not_match">密码不一致</string> <string name="invalid_jid">该 Jabber ID 无效</string> <string name="error_out_of_memory">空间不足。图片过大</string> - <string name="add_phone_book_text">您将添加 %s 至手机联系人列表?</string> + <string name="add_phone_book_text">是否添加 %s 到地址薄?</string> <string name="contact_status_online">在线</string> <string name="contact_status_free_to_chat">自由畅聊</string> <string name="contact_status_away">离开</string> @@ -181,22 +184,24 @@ <string name="muc_details_other_members">其他成员</string> <string name="server_info_show_more">服务器信息</string> <string name="server_info_mam">XEP-0313: MAM</string> - <string name="server_info_carbon_messages">XEP-0280: 消息碳</string> + <string name="server_info_carbon_messages">XEP-0280: 消息复写</string> <string name="server_info_csi">XEP-0352: 客户端状态指示</string> <string name="server_info_blocking">XEP-0191: 屏蔽指令</string> - <string name="server_info_roster_version">XEP-0237: 花名册版本</string> + <string name="server_info_roster_version">XEP-0237: 花名册版本控制</string> <string name="server_info_stream_management">XEP-0198: 流管理</string> - <string name="server_info_pep">XEP-0163: PEP (头像)</string> + <string name="server_info_pep">XEP-0163: PEP (替身 / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP 文件上传</string> + <string name="server_info_push">XEP-0357: Push</string> <string name="server_info_available">有效</string> <string name="server_info_unavailable">无效</string> <string name="missing_public_keys">缺少公钥通知</string> - <string name="last_seen_now">最近一次查看为刚刚</string> - <string name="last_seen_min"> 最近一次查看为一分钟前</string> - <string name="last_seen_mins">最近一次查看为 %d 分钟前</string> - <string name="last_seen_hour">最近一次查看为一小时前</string> - <string name="last_seen_hours">最近一次查看为 %d 小时前</string> - <string name="last_seen_day">最近一次查看为一天前</string> - <string name="last_seen_days">最近一次查看为 %d天前</string> + <string name="last_seen_now">刚刚查看过</string> + <string name="last_seen_min">1 分钟前查看过</string> + <string name="last_seen_mins">%d 分钟前查看过</string> + <string name="last_seen_hour">1 小时前查看过</string> + <string name="last_seen_hours">%d 小时前查看过</string> + <string name="last_seen_day">1 天前查看过</string> + <string name="last_seen_days">%d 天前查看过</string> <string name="never_seen">未曾查看</string> <string name="install_openkeychain">加密信息. 请安装 OpenKeychain 以解密。</string> <string name="unknown_otr_fingerprint">未知 OTR 指纹</string> @@ -204,17 +209,28 @@ <string name="reception_failed">接收失败</string> <string name="your_fingerprint">你的指纹</string> <string name="otr_fingerprint">OTR 指纹</string> + <string name="omemo_fingerprint">OMEMO 指纹</string> + <string name="omemo_fingerprint_x509">v\\OMEMO 指纹</string> + <string name="omemo_fingerprint_selected_message">消息的 OMEMO 指纹</string> + <string name="omemo_fingerprint_x509_selected_message">消息的 OMEMO 指纹</string> + <string name="this_device_omemo_fingerprint">自己的 OMEMO 指纹</string> + <string name="other_devices">其他设备</string> + <string name="trust_omemo_fingerprints">信任的 OMEMO 指纹</string> + <string name="fetching_keys">获取密钥中</string> + <string name="done">完成</string> <string name="verify">验证</string> <string name="decrypt">解密</string> <string name="conferences">讨论组</string> <string name="search">查找</string> <string name="create_contact">创建联系人</string> + <string name="enter_contact">输入联系人</string> <string name="join_conference">加入讨论组</string> <string name="delete_contact">删除联系人</string> <string name="view_contact_details">查看联系人详细信息</string> <string name="block_contact">屏蔽联系人</string> <string name="unblock_contact">解除联系人屏蔽</string> <string name="create">创建</string> + <string name="select">选择</string> <string name="contact_already_exists">联系人已存在</string> <string name="join">加入</string> <string name="conference_address">讨论组地址</string> @@ -225,6 +241,7 @@ <string name="you">你</string> <string name="action_edit_subject">编辑讨论组主题</string> <string name="conference_not_found">讨论组未找到</string> + <string name="conference_unknown_error">收到未知错误</string> <string name="leave">离开</string> <string name="contact_added_you">联系人已添加你到联系人列表</string> <string name="add_back">反向添加</string> @@ -238,7 +255,7 @@ <string name="error_saving_avatar">不能将头像保存至磁盘</string> <string name="or_long_press_for_default">(或长按按钮将返回默认头像)</string> <string name="error_publish_avatar_no_server_support">您的服务器不支持发布头像</string> - <string name="private_message">密谈</string> + <string name="private_message">私聊</string> <string name="private_message_to">至 %s</string> <string name="send_private_message_to">发送私密消息到 %s</string> <string name="connect">连接</string> @@ -246,10 +263,9 @@ <string name="next">下一步</string> <string name="server_info_session_established">当前会话已建立</string> <string name="additional_information">其他信息</string> - <string name="skip">Skip略过</string> + <string name="skip">忽略</string> <string name="disable_notifications">关闭通知</string> <string name="disable_notifications_for_this_conversation">关闭该会话消息</string> - <string name="notifications_disabled">通知已关闭</string> <string name="enable">打开通知</string> <string name="conference_requires_password">讨论组设有密码</string> <string name="enter_password">输入密码</string> @@ -260,15 +276,17 @@ <string name="sure_delete_fingerprint">是否确定删除该指纹?</string> <string name="ignore">忽略</string> <string name="without_mutual_presence_updates"><b>警告:</b>在没有相互更新在线联系人的情况下发送将会出现未知问题。\n\n<small>前往联系人详情以验证您订阅的在线联系人。</small></string> - <string name="pref_encryption_settings">加密设置</string> + <string name="pref_security_settings">安全</string> <string name="pref_force_encryption">强制要求端对端加密</string> <string name="pref_force_encryption_summary"> 总是发送加密信息(讨论组信息除外)</string> + <string name="pref_allow_message_correction">允许更正消息</string> + <string name="pref_allow_message_correction_summary">允许您的联系人追回编辑他们的信息</string> <string name="pref_dont_save_encrypted">不保存加密信息</string> <string name="pref_dont_save_encrypted_summary">警告:此操作将会导致信息丢失</string> - <string name="pref_expert_options">导出选项</string> + <string name="pref_expert_options">高级设置</string> <string name="pref_expert_options_summary">请谨慎使用</string> <string name="title_activity_about">关于 Conversations</string> - <string name="pref_about_conversations_summary">构建及许可证信息</string> + <string name="pref_about_conversations_summary">编译及许可证信息</string> <string name="title_pref_quiet_hours">静默时间段</string> <string name="title_pref_quiet_hours_start_time">开始时间</string> <string name="title_pref_quiet_hours_end_time">结束时间</string> @@ -283,16 +301,22 @@ <string name="pref_expert_options_other">其他</string> <string name="pref_conference_name">讨论组名称</string> <string name="pref_conference_name_summary">用讨论组的主题来标示讨论组而不是 JID</string> + <string name="pref_autojoin">自动加入讨论组</string> + <string name="pref_autojoin_summary">将讨论组加入到自动加入书签</string> <string name="toast_message_otr_fingerprint">OTR 指纹已拷贝到剪贴板!</string> + <string name="toast_message_omemo_fingerprint">OMEMO 指纹已拷贝到剪贴板!</string> <string name="conference_banned">你被此讨论组屏蔽</string> <string name="conference_members_only">此讨论组只允许成员加入</string> <string name="conference_kicked">你被从此讨论组踢出</string> <string name="using_account">用账户 %s</string> + <string name="checking_x">正在 HTTP 服务器中检查 %s</string> <string name="not_connected_try_again">你没有连接。请稍后重试</string> + <string name="check_x_filesize">检查 %s 大小</string> <string name="message_options">消息选项</string> <string name="copy_text">拷贝文本</string> <string name="copy_original_url">拷贝原始URL</string> <string name="send_again">再次发送</string> + <string name="file_url">文件 </string> <string name="message_text">消息文本</string> <string name="url_copied_to_clipboard">已经拷贝 URL 到剪贴板</string> <string name="message_copied_to_clipboard">消息已经拷贝到剪贴板</string> @@ -304,7 +328,6 @@ <string name="verify_otr">验证 OTR</string> <string name="remote_fingerprint">远程指纹</string> <string name="scan">扫描</string> - <string name="or_touch_phones">(或轻触手机)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">提示或问题</string> <string name="shared_secret_secret">共知的秘密</string> @@ -321,7 +344,10 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">保持前台服务</string> <string name="pref_keep_foreground_service_summary">防止操作系统中断你的连接</string> - <string name="choose_file">关闭文件</string> + <string name="pref_export_logs">导出日志</string> + <string name="pref_export_logs_summary">将日志写入 SD 卡</string> + <string name="notification_export_logs_title">正在将日志写入 SD 卡</string> + <string name="choose_file">选择文件</string> <string name="receiving_x_file">接收中 %1$s (已完成 %2$d%%)</string> <string name="download_x_file">下载 %s</string> <string name="file">文件</string> @@ -336,7 +362,7 @@ <string name="could_not_verify_fingerprint">不能验证指纹</string> <string name="manually_verify">手工验证</string> <string name="are_you_sure_verify_fingerprint">你确认验证你的联系人的 OTR 指纹?</string> - <string name="pref_show_dynamic_tags">现实动态标签</string> + <string name="pref_show_dynamic_tags">显示动态标签</string> <string name="pref_show_dynamic_tags_summary">在联系人下方显示只读标签</string> <string name="enable_notifications">启用通知</string> <string name="conference_with">与…创建讨论组</string> @@ -347,12 +373,23 @@ <string name="reset">重置</string> <string name="account_image_description">账户头像</string> <string name="copy_otr_clipboard_description">拷贝 OTR 指纹到剪贴板</string> + <string name="copy_omemo_clipboard_description">拷贝 OMEMO 指纹到剪贴板</string> + <string name="regenerate_omemo_key">重新生成 OMEMO 密钥</string> + <string name="wipe_omemo_pep">从 PEP 中清除其他设备</string> + <string name="clear_other_devices">清除设备</string> + <string name="clear_other_devices_desc">你想清除所有其他设备的 OMEMO 通告?下次你的设备连接,将会重新收到通告,但也许将不会收到当时你发送的消息。</string> + <string name="purge_key">清除密钥</string> + <string name="purge_key_desc_part1">是否确认清除该密钥?</string> + <string name="purge_key_desc_part2">这是不可逆的损坏,你不能用此再建立一个会话了。</string> + <string name="error_no_keys_to_trust_server_error">此联系人没有可用的密钥。\n从服务器获取密钥失败。也许你的联系人所在服务器发生问题。</string> + <string name="error_no_keys_to_trust">此联系人没有可用的密钥。如果你曾经清除过他们的密钥,那么需要他们生成新的密钥。</string> + <string name="error_trustkeys_title">错误</string> <string name="fetching_history_from_server">从服务器获取历史记录</string> <string name="no_more_history_on_server">服务器上没有更多历史记录</string> <string name="updating">更新中…</string> <string name="password_changed">密码已修改!</string> <string name="could_not_change_password">不能修改密码</string> - <string name="otr_session_not_started">要启动加密聊天先发送一条消息</string> + <string name="otr_session_not_started">发送消息来开始加密聊天</string> <string name="ask_question">提出问题</string> <string name="smp_explain_question">如果你和你的联系人有一个共知的秘密(比如一个内部笑话或者仅仅只是上次见面时吃的午餐) 你可以使用这个秘密来验证彼此的指纹。\n\n你的联系人将以大小写敏感的方式给出答案,你可以给出提示或问题。</string> <string name="smp_explain_answer">你的联系人可以通过一个你们共知的秘密来验证指纹。你的联系人给出了如下的提示或问题。</string> @@ -365,7 +402,7 @@ <string name="password_should_not_be_empty">密码不能为空</string> <string name="enable_all_accounts">启用所有账户</string> <string name="disable_all_accounts">禁用所有账户</string> - <string name="perform_action_with">做一个动作和</string> + <string name="perform_action_with">选择一个操作</string> <string name="no_affiliation">没有从属关系</string> <string name="no_role">没有角色</string> <string name="outcast">抛弃</string> @@ -384,8 +421,10 @@ <string name="public_conference">公开访问的讨论组</string> <string name="private_conference">私密,只有成员可以加入的讨论组</string> <string name="conference_options">讨论组选项</string> - <string name="members_only">私密(只对成员开放)</string> + <string name="members_only">私密,只有成员可以加入</string> <string name="non_anonymous">非匿名</string> + <string name="moderated">版主</string> + <string name="you_are_not_participating">您尚未参与</string> <string name="modified_conference_options">讨论组选项已修改!</string> <string name="could_not_modify_conference_options">不能修改讨论组选项</string> <string name="never">从不</string> @@ -394,7 +433,7 @@ <string name="two_hours">2 个小时</string> <string name="eight_hours">8 个小时</string> <string name="until_further_notice">直到新的通知</string> - <string name="pref_input_options">输入选项</string> + <string name="pref_input_options">输入</string> <string name="pref_enter_is_send">回车是发送</string> <string name="pref_enter_is_send_summary">用回车键来发送消息</string> <string name="pref_display_enter_key">显示回车键</string> @@ -406,14 +445,14 @@ <string name="apk">Android App</string> <string name="vcard">联系人</string> <string name="received_x_file">已经收到 %s</string> - <string name="disable_foreground_service">禁用前端服务</string> + <string name="disable_foreground_service">禁用前台服务</string> <string name="touch_to_open_conversations">轻触打开 Conversations</string> <string name="avatar_has_been_published">头像已经发布!</string> <string name="sending_x_file">发送中 %s</string> <string name="offering_x_file">提供中 %s</string> <string name="hide_offline">隐藏离线联系人</string> <string name="disable_account">禁用账户</string> - <string name="contact_is_typing">%s 正在输入…</string> + <string name="contact_is_typing">%s 正在输入</string> <string name="contact_has_stopped_typing">%s 已停止输入</string> <string name="pref_chat_states">键盘输入通知</string> <string name="pref_chat_states_summary">让对方知道你正在输入新消息</string> @@ -424,7 +463,6 @@ <string name="received_location">位置已收到</string> <string name="title_undo_swipe_out_conversation">Conversation 已关闭</string> <string name="title_undo_swipe_out_muc">离开讨论组</string> - <string name="pref_certificate_options">证书选项</string> <string name="pref_dont_trust_system_cas_title">不相信系统 CA</string> <string name="pref_dont_trust_system_cas_summary">所有证书必须人工通过</string> <string name="pref_remove_trusted_certificates_title">移除证书</string> @@ -444,4 +482,76 @@ <string name="none">无</string> <string name="recently_used">最近使用过的</string> <string name="choose_quick_action">选择快速动作</string> + <string name="search_for_contacts_or_groups">搜索联系人或群组</string> + <string name="send_private_message">发送私密消息</string> + <string name="user_has_left_conference">%s 已离开讨论组!</string> + <string name="username">用户名</string> + <string name="username_hint">用户名</string> + <string name="invalid_username">该用户名无效</string> + <string name="download_failed_server_not_found">下载失败:未找到服务器</string> + <string name="download_failed_file_not_found">下载失败:未找到文件</string> + <string name="download_failed_could_not_connect">下载失败:无法连接到服务器</string> + <string name="account_status_tor_unavailable">Tor network 不可用</string> + <string name="server_info_broken">损坏</string> + <string name="pref_presence_settings">存在</string> + <string name="pref_away_when_screen_off">关闭屏幕时离开</string> + <string name="pref_away_when_screen_off_summary">当屏幕关闭时将标记您的资源为离开状态</string> + <string name="pref_xa_on_silent_mode">静音模式时不可用</string> + <string name="pref_xa_on_silent_mode_summary">当设备进入静音模式时把资源标识改为不可用</string> + <string name="pref_show_connection_options">高级边接设置</string> + <string name="pref_show_connection_options_summary">注册账户时显示主机名和端口</string> + <string name="hostname_example">xmpp.example.com</string> + <string name="action_add_account_with_certificate">使用证书添加账户</string> + <string name="unable_to_parse_certificate">无法解析证书</string> + <string name="authenticate_with_certificate">留空以认证 w/ 证书</string> + <string name="mam_prefs">压缩设置</string> + <string name="server_side_mam_prefs">服务端压缩设置</string> + <string name="fetching_mam_prefs">正在获取压缩设置。请稍后...</string> + <string name="unable_to_fetch_mam_prefs">获取压缩设置失败</string> + <string name="captcha_ocr">验证码</string> + <string name="captcha_required">需要验证码</string> + <string name="captcha_hint">输入图片中的文字</string> + <string name="certificate_chain_is_not_trusted">证书链不受信任</string> + <string name="jid_does_not_match_certificate">Jabber ID 与证书不匹配</string> + <string name="action_renew_certificate">更新证书</string> + <string name="error_fetching_omemo_key">获取 OMEMO 密钥错误!</string> + <string name="verified_omemo_key_with_certificate">请用证书验证 OMEMO 密钥!</string> + <string name="device_does_not_support_certificates">您的设备不支持设备证书选择!</string> + <string name="pref_connection_options">连接</string> + <string name="account_settings_hostname">主机名</string> + <string name="account_settings_port">端口</string> + <string name="not_a_valid_port">该端口号无效</string> + <string name="not_valid_hostname">该主机名无效</string> + <string name="connected_accounts">%2$d 个中的 %1$d 个账户已连接</string> + <plurals name="x_messages"> + <item quantity="other">%d 条消息</item> + </plurals> + <string name="shared_file_with_x">用 %s 分享文件</string> + <string name="shared_image_with_x">用 %s 分享图片</string> + <string name="no_storage_permission">Conversations 需要访问外部存储</string> + <string name="sync_with_contacts">与联系人同步</string> + <string name="sync_with_contacts_long">Conversations 会匹配你的 XMPP 花名册与你的联系人,以显示他们的全名和头像。\n\nConversations 只会读取你的联系人并在本地匹配,不会上传到你的服务器。\n\n现在将要询问你是否给予访问你联系人的权限。</string> + <string name="certificate_information">证书详情</string> + <string name="certificate_subject">主题</string> + <string name="certificate_issuer">发行人</string> + <string name="certificate_cn">通用名称</string> + <string name="certificate_o">组织</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(不可用)</string> + <string name="certificate_not_found">未发现证书</string> + <string name="notify_on_all_messages">为所有信息显示通知</string> + <string name="notify_only_when_highlighted">仅当高亮时显示通知</string> + <string name="notify_never">禁用通知</string> + <string name="notify_paused">暂停通知</string> + <string name="always">总是</string> + <string name="automatically">自动</string> + <string name="battery_optimizations_enabled">启用节电模式</string> + <string name="battery_optimizations_enabled_explained">你的设备正在为Conversations进行电池优化,这可能导致通知的延迟甚至消息的丢失。 +建议不要这样做</string> + <string name="battery_optimizations_enabled_dialog">你的设备正在为Conversations进行电池优化,这可能导致通知的延迟甚至消息的丢失。 +你将会被提示禁用该功能。</string> + <string name="disable">禁用</string> + <string name="selection_too_large">选择区域过大</string> + <string name="no_accounts">(没有激活的账户)</string> + <string name="this_field_is_required">必填</string> </resources> diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml index 95d9e4e6..9a59cfe6 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -8,7 +8,6 @@ <string name="action_secure">安全對話</string> <string name="action_add_account">新增帳戶</string> <string name="action_edit_contact">編輯姓名</string> - <string name="action_add_phone_book">新增到手機通訊錄</string> <string name="action_delete_contact">從列表中刪除</string> <string name="title_activity_manage_accounts">管理帳戶</string> <string name="title_activity_conference_details">群組詳情</string> @@ -21,7 +20,6 @@ <string name="minutes_ago">%d 分鐘前</string> <string name="unread_conversations">未讀對話</string> <string name="sending">正在發送…</string> - <string name="encrypted_message">正在解密訊息中,請稍候…</string> <string name="nick_in_use">該用戶名稱已被使用</string> <string name="admin">管理員</string> <string name="owner">擁有人</string> @@ -58,9 +56,7 @@ <string name="clear_conversation_history">清除對話記錄</string> <string name="clear_histor_msg">你確定要刪除該對話中所有訊息嗎?\n\n<b>警告:</b> 這將不會影響其他設備或伺服器儲存的訊息。</string> <string name="delete_messages">刪除訊息</string> - <string name="also_end_conversation">之後結束這對話</string> <string name="choose_presence">選擇狀態訊息</string> - <string name="send_plain_text_message">發送純文字訊息</string> <string name="send_otr_message">發送 OTR 加密訊息</string> <string name="send_pgp_message">發送 OpenPGP 加密訊息</string> <string name="your_nick_has_been_changed">用戶名稱修改成功</string> @@ -76,29 +72,25 @@ <string name="contact_has_no_pgp_key">Conversations 不能將你的訊息加密,因為聯絡人沒有公佈他/她的公鑰。\n\n<small>請通知聯絡人設定 OpenPGP。</small></string> <string name="no_pgp_keys">找不到多條 OpenPGP 鑰匙</string> <string name="contacts_have_no_pgp_keys">Conversations 不能將你的訊息加密,因為多位聯絡人沒有公佈他/她的公鑰。\n\n<small>請通知聯絡人設定 OpenPGP。</small></string> - <string name="encrypted_message_received"><i>已收到加密訊息,點擊進行解密和查看。</i></string> <string name="pref_general">一般</string> <string name="pref_xmpp_resource">XMPP 資源</string> <string name="pref_xmpp_resource_summary">客戶端標示名稱</string> <string name="pref_accept_files">接收文件</string> <string name="pref_accept_files_size_summary">自動接收小於 … 的文件</string> <string name="pref_notification_settings">通知設定</string> + <string name="pref_accept_files_summary">自動接收小於 … 的文件</string> <string name="pref_notifications">通知</string> <string name="pref_notifications_summary">收到新訊息時通知</string> <string name="pref_vibrate">震動</string> <string name="pref_vibrate_summary">收到新訊息時震動</string> <string name="pref_sound">聲音</string> <string name="pref_sound_summary">收到新訊息時播放鈴聲</string> - <string name="pref_conference_notifications">群組通知</string> - <string name="pref_conference_notifications_summary">當有新訊息時總是通知,而不是被標記時才通知</string> <string name="pref_notification_grace_period">通知限期</string> <string name="pref_notification_grace_period_summary">收到副本後,關閉通知一小段時間</string> - <string name="pref_advanced_options">進階選項</string> <string name="pref_never_send_crash">總是不發送故障報告</string> <string name="pref_never_send_crash_summary">發送「堆疊追蹤」給 Conversations 的開發人員能幫助改進本程式</string> <string name="pref_confirm_messages">確認訊息</string> <string name="pref_confirm_messages_summary">讓你的聯絡人知道你已收到及閱讀訊息</string> - <string name="pref_ui_options">介面選項</string> <string name="openpgp_error">OpenKeychain 回報了一個錯誤</string> <string name="error_decrypting_file">解密文件時出現 I/O 錯誤</string> <string name="accept">接受</string> @@ -131,7 +123,6 @@ <string name="account_status_regis_conflict">該用戶名稱已被使用</string> <string name="account_status_regis_success">註冊完成</string> <string name="account_status_regis_not_sup">伺服器不支持註冊</string> - <string name="encryption_choice_none">純文字內容</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> <string name="mgmt_account_edit">編輯帳戶</string> @@ -152,7 +143,6 @@ <string name="passwords_do_not_match">密碼不一致</string> <string name="invalid_jid">該 Jabber ID 無效</string> <string name="error_out_of_memory">空間不足,圖片過大</string> - <string name="add_phone_book_text">你確定要新增 %s 為聯絡人嗎?</string> <string name="contact_status_online">線上</string> <string name="contact_status_free_to_chat">目前有空</string> <string name="contact_status_away">離開</string> @@ -163,7 +153,6 @@ <string name="muc_details_other_members">其他成員</string> <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> <string name="server_info_available">支援</string> <string name="server_info_unavailable">不支援</string> <string name="missing_public_keys">沒有公佈公鑰訊息。</string> @@ -224,7 +213,6 @@ <string name="skip">略過</string> <string name="disable_notifications">關閉通知</string> <string name="disable_notifications_for_this_conversation">關閉該對話消息</string> - <string name="notifications_disabled">通知已關閉</string> <string name="enable">打開通知</string> <string name="conference_requires_password">群組設有密碼</string> <string name="enter_password">輸入密碼</string> @@ -235,12 +223,10 @@ <string name="sure_delete_fingerprint">你確定刪除該指紋嗎?</string> <string name="ignore">忽略</string> <string name="without_mutual_presence_updates"><b>警告:</b> 在沒有互相關注狀態訊息的情況下發送或會引起不能預計的問題。\n\n<small>請檢視聯絡人詳情頁面以確認你們的關注狀態。</small></string> - <string name="pref_encryption_settings">加密設定</string> <string name="pref_force_encryption">強制要求端到端加密</string> <string name="pref_force_encryption_summary">總是發送加密訊息 (群組訊息除外)</string> <string name="pref_dont_save_encrypted">不儲存加密訊息</string> <string name="pref_dont_save_encrypted_summary">警告: 此操作或會導致訊息丟失</string> - <string name="pref_expert_options">專家選項</string> <string name="pref_expert_options_summary">請小心設定</string> <string name="pref_use_larger_font">增加字體大小</string> <string name="pref_use_larger_font_summary">讓整個 app 界面使用更大號的字體</string> diff --git a/src/main/res/values/arrays.xml b/src/main/res/values/arrays.xml index 24f8790c..f8028ea3 100644 --- a/src/main/res/values/arrays.xml +++ b/src/main/res/values/arrays.xml @@ -79,4 +79,10 @@ <item>ALWAYS</item> <item>NEVER</item> </string-array> + + <string-array name="mam_prefs"> + <item>@string/never</item> + <item>@string/contacts</item> + <item>@string/always</item> + </string-array> </resources> diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml index 7a195aa4..0d6d19b1 100644 --- a/src/main/res/values/attrs.xml +++ b/src/main/res/values/attrs.xml @@ -14,6 +14,7 @@ <attr name="icon_download" format="reference"/> <attr name="icon_edit" format="reference"/> <attr name="icon_edit_dark" format="reference"/> + <attr name="icon_done" format="reference"/> <attr name="icon_group" format="reference"/> <attr name="icon_new" format="reference"/> <attr name="icon_new_attachment" format="reference"/> diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml index 9cef1b0a..2d744395 100644 --- a/src/main/res/values/colors.xml +++ b/src/main/res/values/colors.xml @@ -9,17 +9,22 @@ <color name="online">@color/green500</color> <color name="notification">@color/green500</color> - <color name="green500">#ff259b24</color> + <color name="primary">@color/green500</color> + <color name="primary_dark">@color/green700</color> + <color name="accent">#ff0091ea</color> + <color name="black87">#de000000</color> + <color name="black54">#8a000000</color> + <color name="black26">#42000000</color> + <color name="black12">#1f000000</color> + <color name="white">#ffffffff</color> + <color name="white70">#b2ffffff</color> + <color name="grey50">#fffafafa</color> + <color name="grey200">#ffeeeeee</color> + <color name="grey500">#ff9e9e9e</color> + <color name="grey800">#ff424242</color> + <color name="red500">#fff44336</color> + <color name="red800">#ffc62828</color> + <color name="orange500">#ffff9800</color> + <color name="green500">#ff259b24</color> <color name="green700">#ff0a7e07</color> - <color name="accent">#ff0091ea</color> - <color name="black87">#de000000</color> - <color name="black54">#8a000000</color> - <color name="black12">#1f000000</color> - <color name="white">#ffffffff</color> - <color name="white70">#b2ffffff</color> - <color name="grey50">#fffafafa</color> - <color name="grey200">#ffeeeeee</color> - <color name="grey800">#ff424242</color> - <color name="red500">#fff44336</color> - <color name="orange500">#ffff9800</color> </resources>
\ No newline at end of file diff --git a/src/main/res/values/encryption_settings.xml b/src/main/res/values/encryption_settings.xml new file mode 100644 index 00000000..33db8387 --- /dev/null +++ b/src/main/res/values/encryption_settings.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <bool name="omemo_enabled">false</bool> +</resources>
\ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 8885622b..15b7efc5 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -1,290 +1,304 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - - <string name="app_name" translatable="false">Conversations+</string> - <string name="action_settings">Settings</string> - <string name="action_add">New conversation</string> - <string name="action_accounts">Manage accounts</string> - <string name="action_end_conversation">End this conversation</string> - <string name="action_contact_details">Contact details</string> - <string name="action_muc_details">Conference details</string> - <string name="action_secure">Secure conversation</string> - <string name="action_add_account">Add account</string> - <string name="action_edit_contact">Edit name</string> - <string name="action_add_phone_book">Add to phone book</string> - <string name="action_delete_contact">Delete from roster</string> - <string name="action_block_contact">Block contact</string> - <string name="action_unblock_contact">Unblock contact</string> - <string name="action_block_domain">Block domain</string> - <string name="action_unblock_domain">Unblock domain</string> - <string name="title_activity_manage_accounts">Manage Accounts</string> - <string name="title_activity_settings">Settings</string> - <string name="title_activity_conference_details">Conference Details</string> - <string name="title_activity_contact_details">Contact Details</string> - <string name="title_activity_sharewith">Share with Conversation</string> - <string name="title_activity_start_conversation">Start Conversation</string> - <string name="title_activity_choose_contact">Choose contact</string> - <string name="title_activity_block_list">Block list</string> - <string name="just_now">just now</string> - <string name="minute_ago">1 min ago</string> - <string name="minutes_ago">%d mins ago</string> - <string name="unread_conversations">unread Conversations</string> - <string name="sending">sending…</string> - <string name="encrypted_message">Decrypting message. Please wait…</string> - <string name="nick_in_use">Nickname is already in use</string> - <string name="admin">Admin</string> - <string name="owner">Owner</string> - <string name="moderator">Moderator</string> - <string name="participant">Participant</string> - <string name="visitor">Visitor</string> - <string name="remove_contact_text">Would you like to remove %s from your roster? The conversation associated with this contact will not be removed.</string> - <string name="block_contact_text">Would you like to block %s from sending you messages?</string> - <string name="unblock_contact_text">Would you like to unblock %s and allow them to send you messages?</string> - <string name="block_domain_text">Block all contacts from %s?</string> - <string name="unblock_domain_text">Unblock all contacts from %s?</string> - <string name="contact_blocked">Contact blocked</string> - <string name="remove_bookmark_text">Would you like to remove %s as a bookmark? The conversation associated with this bookmark will not be removed.</string> - <string name="register_account">Register new account on server</string> - <string name="change_password_on_server">Change password on server</string> - <string name="share_with">Share with…</string> - <string name="start_conversation">Start Conversation</string> - <string name="invite_contact">Invite Contact</string> - <string name="contacts">Contacts</string> - <string name="cancel">Cancel</string> - <string name="set">Set</string> - <string name="add">Add</string> - <string name="edit">Edit</string> - <string name="delete">Delete</string> - <string name="block">Block</string> - <string name="unblock">Unblock</string> - <string name="save">Save</string> - <string name="ok">OK</string> - <string name="crash_report_title">Conversations has crashed</string> - <string name="crash_report_message">By sending in stack traces you are helping the ongoing development of Conversations\n<b>Warning:</b> This will use your XMPP account to send the stack trace to the developer.</string> - <string name="send_now">Send now</string> - <string name="send_never">Never ask again</string> - <string name="problem_connecting_to_account">Unable to connect to account</string> - <string name="problem_connecting_to_accounts">Unable to connect to multiple accounts</string> - <string name="touch_to_fix">Touch here to manage your accounts</string> - <string name="attach_file">Attach file</string> - <string name="not_in_roster">The contact is not in your roster. Would you like to add it?</string> - <string name="add_contact">Add contact</string> - <string name="send_failed">delivery failed</string> - <string name="send_rejected">rejected</string> - <string name="preparing_image">Preparing image for transmission</string> - <string name="action_clear_history">Clear history</string> - <string name="clear_conversation_history">Clear Conversation History</string> - <string name="clear_histor_msg">Do you want to delete all messages within this Conversation?\n\n<b>Warning:</b> This will not influence messages stored on other devices or servers.</string> - <string name="delete_messages">Delete messages</string> - <string name="also_end_conversation">End this conversations afterwards</string> - <string name="choose_presence">Choose presence to contact</string> - <string name="send_plain_text_message">Send plain text message</string> - <string name="send_otr_message">Send OTR encrypted message</string> - <string name="send_pgp_message">Send OpenPGP encrypted message</string> - <string name="your_nick_has_been_changed">Your nickname has been changed</string> - <string name="download_image">Download Image</string> - <string name="send_unencrypted">Send unencrypted</string> - <string name="decryption_failed">Decryption failed. Maybe you don’t have the proper private key.</string> - <string name="openkeychain_required">OpenKeychain</string> - <string name="openkeychain_required_long">Conversations utilizes a third party app called <b>OpenKeychain</b> to encrypt and decrypt messages and to manage your public keys.\n\nOpenKeychain is licensed under GPLv3 and available on F-Droid and Google Play.\n\n<small>(Please restart Conversations afterwards.)</small></string> - <string name="restart">Restart</string> - <string name="install">Install</string> - <string name="offering">offering…</string> - <string name="waiting">waiting…</string> - <string name="no_pgp_key">No OpenPGP Key found</string> - <string name="contact_has_no_pgp_key">Conversations is unable to encrypt your messages because your contact is not announcing his or hers public key.\n\n<small>Please ask your contact to setup OpenPGP.</small></string> - <string name="no_pgp_keys">No OpenPGP Keys found</string> - <string name="contacts_have_no_pgp_keys">Conversations is unable to encrypt your messages because your contacts are not announcing their public key.\n\n<small>Please ask your contacts to setup OpenPGP.</small></string> - <string name="encrypted_message_received"><i>Encrypted message received. Touch to view and decrypt.</i></string> - <string name="pref_general">General</string> - <string name="pref_xmpp_resource">XMPP resource</string> - <string name="pref_xmpp_resource_summary">The name this client identifies itself with</string> - <string name="pref_accept_files">Accept files</string> - <string name="pref_accept_files_summary">Settings for accepting and automatically downloading files</string> + <string name="app_name" translatable="false">Conversations+</string> + <string name="action_settings">Settings</string> + <string name="action_add">New conversation</string> + <string name="action_accounts">Manage accounts</string> + <string name="action_end_conversation">End this conversation</string> + <string name="action_contact_details">Contact details</string> + <string name="action_muc_details">Conference details</string> + <string name="action_secure">Secure conversation</string> + <string name="action_add_account">Add account</string> + <string name="action_edit_contact">Edit name</string> + <string name="action_add_phone_book">Add to address book</string> + <string name="action_delete_contact">Delete from roster</string> + <string name="action_block_contact">Block contact</string> + <string name="action_unblock_contact">Unblock contact</string> + <string name="action_block_domain">Block domain</string> + <string name="action_unblock_domain">Unblock domain</string> + <string name="title_activity_manage_accounts">Manage Accounts</string> + <string name="title_activity_settings">Settings</string> + <string name="title_activity_conference_details">Conference Details</string> + <string name="title_activity_contact_details">Contact Details</string> + <string name="title_activity_sharewith">Share with Conversation</string> + <string name="title_activity_start_conversation">Start Conversation</string> + <string name="title_activity_choose_contact">Choose contact</string> + <string name="title_activity_block_list">Block list</string> + <string name="just_now">just now</string> + <string name="minute_ago">1 min ago</string> + <string name="minutes_ago">%d mins ago</string> + <string name="unread_conversations">unread Conversations</string> + <string name="sending">sending…</string> + <string name="message_decrypting">Decrypting message. Please wait…</string> + <string name="pgp_message">OpenPGP encrypted message</string> + <string name="nick_in_use">Nickname is already in use</string> + <string name="admin">Admin</string> + <string name="owner">Owner</string> + <string name="moderator">Moderator</string> + <string name="participant">Participant</string> + <string name="visitor">Visitor</string> + <string name="remove_contact_text">Would you like to remove %s from your roster? The conversation associated with this contact will not be removed.</string> + <string name="block_contact_text">Would you like to block %s from sending you messages?</string> + <string name="unblock_contact_text">Would you like to unblock %s and allow them to send you messages?</string> + <string name="block_domain_text">Block all contacts from %s?</string> + <string name="unblock_domain_text">Unblock all contacts from %s?</string> + <string name="contact_blocked">Contact blocked</string> + <string name="remove_bookmark_text">Would you like to remove %s as a bookmark? The conversation associated with this bookmark will not be removed.</string> + <string name="register_account">Register new account on server</string> + <string name="change_password_on_server">Change password on server</string> + <string name="share_with">Share with…</string> + <string name="start_conversation">Start Conversation</string> + <string name="invite_contact">Invite Contact</string> + <string name="contacts">Contacts</string> + <string name="cancel">Cancel</string> + <string name="set">Set</string> + <string name="add">Add</string> + <string name="edit">Edit</string> + <string name="delete">Delete</string> + <string name="block">Block</string> + <string name="unblock">Unblock</string> + <string name="save">Save</string> + <string name="ok">OK</string> + <string name="crash_report_title">Conversations has crashed</string> + <string name="crash_report_message">By sending in stack traces you are helping the ongoing development of Conversations\n<b>Warning:</b> This will use your XMPP account to send the stack trace to the developer.</string> + <string name="send_now">Send now</string> + <string name="send_never">Never ask again</string> + <string name="problem_connecting_to_account">Unable to connect to account</string> + <string name="problem_connecting_to_accounts">Unable to connect to multiple accounts</string> + <string name="touch_to_fix">Touch here to manage your accounts</string> + <string name="attach_file">Attach file</string> + <string name="not_in_roster">The contact is not in your roster. Would you like to add it?</string> + <string name="add_contact">Add contact</string> + <string name="send_failed">delivery failed</string> + <string name="send_rejected">rejected</string> + <string name="preparing_image">Preparing image for transmission</string> + <string name="action_clear_history">Clear history</string> + <string name="clear_conversation_history">Clear Conversation History</string> + <string name="clear_histor_msg">Do you want to delete all messages within this Conversation?\n\n<b>Warning:</b> This will not influence messages stored on other devices or servers.</string> + <string name="delete_messages">Delete messages</string> + <string name="also_end_conversation">End this conversation afterwards</string> + <string name="choose_presence">Choose presence to contact</string> + <string name="send_unencrypted_message">Send unencrypted message</string> + <string name="send_otr_message">Send OTR encrypted message</string> + <string name="send_omemo_message">Send OMEMO encrypted message</string> + <string name="send_omemo_x509_message">Send v\\OMEMO encrypted message</string> + <string name="send_pgp_message">Send OpenPGP encrypted message</string> + <string name="your_nick_has_been_changed">Your nickname has been changed</string> + <string name="send_unencrypted">Send unencrypted</string> + <string name="decryption_failed">Decryption failed. Maybe you don’t have the proper private key.</string> + <string name="openkeychain_required">OpenKeychain</string> + <string name="openkeychain_required_long">Conversations utilizes a third party app called <b>OpenKeychain</b> to encrypt and decrypt messages and to manage your public keys.\n\nOpenKeychain is licensed under GPLv3 and available on F-Droid and Google Play.\n\n<small>(Please restart Conversations afterwards.)</small></string> + <string name="restart">Restart</string> + <string name="install">Install</string> + <string name="openkeychain_not_installed">Please install OpenKeychain</string> + <string name="offering">offering…</string> + <string name="waiting">waiting…</string> + <string name="no_pgp_key">No OpenPGP Key found</string> + <string name="contact_has_no_pgp_key">Conversations is unable to encrypt your messages because your contact is not announcing his or hers public key.\n\n<small>Please ask your contact to setup OpenPGP.</small></string> + <string name="no_pgp_keys">No OpenPGP Keys found</string> + <string name="contacts_have_no_pgp_keys">Conversations is unable to encrypt your messages because your contacts are not announcing their public key.\n\n<small>Please ask your contacts to setup OpenPGP.</small></string> + <string name="pref_general">General</string> + <string name="pref_xmpp_resource">XMPP resource</string> + <string name="pref_xmpp_resource_summary">The name this client identifies itself with</string> + <string name="pref_accept_files">Accept files</string> + <string name="pref_accept_files_summary">Automatically accept files smaller than…</string> <string name="pref_accept_files_size">Size</string> <string name="pref_accept_files_size_summary">Automatically accept files smaller than…</string> <string name="pref_accept_files_download">Wi-Fi only</string> <string name="pref_accept_files_download_summary">Download and accept files automatically only when using Wi-Fi</string> <string name="pref_accept_files_download_link">Image links</string> <string name="pref_accept_files_download_link_summary">Automatically download image links</string> - <string name="pref_notification_settings">Notification Settings</string> - <string name="pref_notifications">Notifications</string> - <string name="pref_notifications_summary">Notify when a new message arrives</string> - <string name="pref_vibrate">Vibrate</string> - <string name="pref_vibrate_summary">Also vibrate when a new message arrives</string> - <string name="pref_sound">Sound</string> - <string name="pref_sound_summary">Play ringtone with notification</string> - <string name="pref_conference_notifications">Conference notifications</string> - <string name="pref_conference_notifications_summary">Always notify when a new conference message arrives instead of only when highlighted</string> - <string name="pref_notification_grace_period">Notification grace period</string> - <string name="pref_notification_grace_period_summary">Disable notifications for a short time after a carbon copy was received</string> - <string name="pref_advanced_options">Advanced Options</string> - <string name="pref_never_send_crash">Never send crash reports</string> - <string name="pref_never_send_crash_summary">By sending in stack traces you are helping the ongoing development of Conversations</string> - <string name="pref_confirm_messages">Confirm Messages</string> - <string name="pref_confirm_messages_summary">Let your contact know when you have received or read a message</string> + <string name="pref_notification_settings">Notification</string> + <string name="pref_notifications">Notifications</string> + <string name="pref_notifications_summary">Notify when a new message arrives</string> + <string name="pref_vibrate">Vibrate</string> + <string name="pref_vibrate_summary">Also vibrate when a new message arrives</string> + <string name="pref_sound">Sound</string> + <string name="pref_sound_summary">Play ringtone with notification</string> + <string name="pref_notification_grace_period">Notification grace period</string> + <string name="pref_notification_grace_period_summary">Disable notifications for a short time after a carbon copy was received</string> + <string name="pref_advanced_options">Advanced</string> + <string name="pref_never_send_crash">Never send crash reports</string> + <string name="pref_never_send_crash_summary">By sending in stack traces you are helping the ongoing development of Conversations</string> + <string name="pref_confirm_messages">Confirm Messages</string> + <string name="pref_confirm_messages_summary">Let your contact know when you have received and read a message</string> <string name="pref_confirm_messages_none">No confirmation</string> <string name="pref_confirm_messages_received">Confirmation for received message</string> <string name="pref_confirm_messages_read_and_received">Confirmation for received and read message</string> - <string name="pref_ui_options">UI Options</string> + <string name="pref_ui_options">UI</string> <string name="pref_parse_emoticons">Parse Emoticons</string> <string name="pref_parse_emoticons_summary">Replace emoticons with smilies.</string> - <string name="openpgp_error">OpenKeychain reported an error</string> - <string name="error_decrypting_file">I/O Error decrypting file</string> - <string name="accept">Accept</string> - <string name="error">An error has occurred</string> - <string name="pref_grant_presence_updates">Grant presence updates</string> - <string name="pref_grant_presence_updates_summary">Preemptively grant and ask for presence subscription for contacts you created</string> - <string name="subscriptions">Subscriptions</string> - <string name="your_account">Your account</string> - <string name="keys">Keys</string> - <string name="send_presence_updates">Send presence updates</string> - <string name="receive_presence_updates">Receive presence updates</string> - <string name="ask_for_presence_updates">Ask for presence updates</string> - <string name="attach_choose_picture">Choose picture</string> - <string name="attach_take_picture">Take picture</string> - <string name="preemptively_grant">Preemptively grant subscription request</string> - <string name="error_not_an_image_file">The file you selected is not an image</string> - <string name="error_compressing_image">Error while converting the image file</string> - <string name="error_file_not_found">File not found</string> - <string name="error_io_exception">General I/O error. Maybe you ran out of storage space?</string> - <string name="error_security_exception_during_image_copy">The app you used to select this image did not provide us with enough permissions to read the file.\n\n<small>Use a different file manager to choose an image</small></string> - <string name="account_status_unknown">Unknown</string> - <string name="account_status_disabled">Temporarily disabled</string> - <string name="account_status_online">Online</string> - <string name="account_status_connecting">Connecting\u2026</string> - <string name="account_status_offline">Offline</string> - <string name="account_status_unauthorized">Unauthorized</string> - <string name="account_status_not_found">Server not found</string> - <string name="account_status_no_internet">No connectivity</string> - <string name="account_status_regis_fail">Registration failed</string> - <string name="account_status_regis_conflict">Username already in use</string> - <string name="account_status_regis_success">Registration completed</string> - <string name="account_status_regis_not_sup">Server does not support registration</string> - <string name="account_status_security_error">Security error</string> - <string name="account_status_incompatible_server">Incompatible server</string> - <string name="encryption_choice_none">Plain text</string> - <string name="encryption_choice_otr">OTR</string> - <string name="encryption_choice_pgp">OpenPGP</string> - <string name="mgmt_account_edit">Edit account</string> - <string name="mgmt_account_delete">Delete account</string> - <string name="mgmt_account_disable">Temporarily disable</string> - <string name="mgmt_account_publish_avatar">Publish avatar</string> - <string name="mgmt_account_publish_pgp">Publish OpenPGP public key</string> - <string name="mgmt_account_enable">Enable account</string> - <string name="mgmt_account_are_you_sure">Are you sure?</string> - <string name="mgmt_account_delete_confirm_text">If you delete your account your entire conversation history will be lost</string> - <string name="attach_record_voice">Record voice</string> - <string name="account_settings_jabber_id">Jabber ID</string> - <string name="account_settings_password">Password</string> - <string name="account_settings_example_jabber_id">username@example.com</string> - <string name="account_settings_confirm_password">Confirm password</string> - <string name="password">Password</string> - <string name="confirm_password">Confirm password</string> - <string name="passwords_do_not_match">Passwords do not match</string> - <string name="invalid_jid">This is not a valid Jabber ID</string> - <string name="error_out_of_memory">Out of memory. Image is too large</string> - <string name="add_phone_book_text">Do you want to add %s to your phones contact list?</string> - <string name="contact_status_online">online</string> - <string name="contact_status_free_to_chat">free to chat</string> - <string name="contact_status_away">away</string> - <string name="contact_status_extended_away">extended away</string> - <string name="contact_status_do_not_disturb">do not disturb</string> - <string name="contact_status_offline">offline</string> - <string name="muc_details_conference">Conference</string> - <string name="muc_details_other_members">Other Members</string> - <string name="server_info_show_more">Server info</string> - <string name="server_info_mam">XEP-0313: MAM</string> - <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> - <string name="server_info_csi">XEP-0352: Client State Indication</string> - <string name="server_info_blocking">XEP-0191: Blocking Command</string> - <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> - <string name="server_info_stream_management">XEP-0198: Stream Management</string> - <string name="server_info_pep">XEP-0163: PEP (Avatars)</string> - <string name="server_info_available">available</string> - <string name="server_info_unavailable">unavailable</string> - <string name="missing_public_keys">Missing public key announcements</string> - <string name="last_seen_now">last seen just now</string> - <string name="last_seen_min">last seen 1 minute ago</string> - <string name="last_seen_mins">last seen %d minutes ago</string> - <string name="last_seen_hour">last seen 1 hour ago</string> - <string name="last_seen_hours">last seen %d hours ago</string> - <string name="last_seen_day">last seen 1 day ago</string> - <string name="last_seen_days">last seen %d days ago</string> - <string name="never_seen">never seen</string> - <string name="install_openkeychain">Encrypted message. Please install OpenKeychain to decrypt.</string> - <string name="unknown_otr_fingerprint">Unknown OTR fingerprint</string> - <string name="openpgp_messages_found">OpenPGP encrypted messages found</string> - <string name="reception_failed">Reception failed</string> - <string name="your_fingerprint">Your fingerprint</string> - <string name="otr_fingerprint">OTR fingerprint</string> - <string name="verify">Verify</string> - <string name="decrypt">Decrypt</string> - <string name="conferences">Conferences</string> - <string name="search">Search</string> - <string name="create_contact">Create Contact</string> - <string name="join_conference">Join Conference</string> - <string name="delete_contact">Delete Contact</string> - <string name="view_contact_details">View contact details</string> - <string name="block_contact">Block contact</string> - <string name="unblock_contact">Unblock contact</string> - <string name="create">Create</string> - <string name="contact_already_exists">The contact already exists</string> - <string name="join">Join</string> - <string name="conference_address">Conference address</string> - <string name="conference_address_example">room@conference.example.com</string> - <string name="save_as_bookmark">Save as bookmark</string> - <string name="delete_bookmark">Delete bookmark</string> - <string name="bookmark_already_exists">This bookmark already exists</string> - <string name="you">You</string> - <string name="action_edit_subject">Edit conference subject</string> - <string name="conference_not_found">Conference not found</string> - <string name="leave">Leave</string> - <string name="contact_added_you">Contact added you to contact list</string> - <string name="add_back">Add back</string> - <string name="contact_has_read_up_to_this_point">%s has read up to this point</string> - <string name="publish">Publish</string> - <string name="touch_to_choose_picture">Touch avatar to select picture from gallery</string> - <string name="publish_avatar_explanation">Please note: Everyone subscribed to your presence updates will be allowed to see this picture.</string> - <string name="publishing">Publishing…</string> - <string name="error_publish_avatar_server_reject">The server rejected your publication</string> - <string name="error_publish_avatar_converting">Something went wrong while converting your picture</string> - <string name="error_saving_avatar">Could not save avatar to disk</string> - <string name="or_long_press_for_default">(Or long press to bring back default)</string> - <string name="error_publish_avatar_no_server_support">Your server does not support the publication of avatars</string> - <string name="private_message">whispered</string> - <string name="private_message_to">to %s</string> - <string name="send_private_message_to">Send private message to %s</string> - <string name="connect">Connect</string> - <string name="account_already_exists">This account already exists</string> - <string name="next">Next</string> - <string name="server_info_session_established">Current session established</string> - <string name="additional_information">Additional Information</string> - <string name="skip">Skip</string> - <string name="disable_notifications">Disable notifications</string> - <string name="disable_notifications_for_this_conversation">Disable notifications for this conversation</string> - <string name="notifications_disabled">Notifications are disabled</string> - <string name="enable">Enable</string> - <string name="conference_requires_password">Conference requires password</string> - <string name="enter_password">Enter password</string> - <string name="missing_presence_updates">Missing presence updates from contact</string> - <string name="request_presence_updates">Please request presence updates from your contact first.\n\n<small>This will be used to determine what client(s) your contact is using.</small></string> - <string name="request_now">Request now</string> - <string name="delete_fingerprint">Delete Fingerprint</string> - <string name="sure_delete_fingerprint">Are you sure you would like to delete this fingerprint?</string> - <string name="ignore">Ignore</string> - <string name="without_mutual_presence_updates"><b>Warning:</b> Sending this without mutual presence updates could cause unexpected problems.\n\n<small>Go to contact details to verify your presence subscriptions.</small></string> - <string name="pref_encryption_settings">Encryption settings</string> - <string name="pref_force_encryption">Force end-to-end encryption</string> - <string name="pref_force_encryption_summary">Always send messages encrypted (except for conferences)</string> - <string name="pref_dont_save_encrypted">Don’t save encrypted messages</string> - <string name="pref_dont_save_encrypted_summary">Warning: This could lead to message loss</string> - <string name="pref_expert_options">Expert options</string> - <string name="pref_expert_options_summary">Please be careful with these</string> - <string name="title_activity_about">About Conversations</string> - <string name="pref_about_conversations_summary">Build and licensing information</string> - <string name="pref_about_message" translatable="false"> - Conversations+ is the improved version of Conversations. + <string name="openpgp_error">OpenKeychain reported an error</string> + <string name="error_decrypting_file">I/O Error decrypting file</string> + <string name="accept">Accept</string> + <string name="error">An error has occurred</string> + <string name="pref_grant_presence_updates">Grant presence updates</string> + <string name="pref_grant_presence_updates_summary">Preemptively grant and ask for presence subscription for contacts you created</string> + <string name="subscriptions">Subscriptions</string> + <string name="your_account">Your account</string> + <string name="keys">Keys</string> + <string name="send_presence_updates">Send presence updates</string> + <string name="receive_presence_updates">Receive presence updates</string> + <string name="ask_for_presence_updates">Ask for presence updates</string> + <string name="attach_choose_picture">Choose picture</string> + <string name="attach_take_picture">Take picture</string> + <string name="preemptively_grant">Preemptively grant subscription request</string> + <string name="error_not_an_image_file">The file you selected is not an image</string> + <string name="error_compressing_image">Error while converting the image file</string> + <string name="error_file_not_found">File not found</string> + <string name="error_io_exception">General I/O error. Maybe you ran out of storage space?</string> + <string name="error_security_exception_during_image_copy">The app you used to select this image did not provide us with enough permissions to read the file.\n\n<small>Use a different file manager to choose an image</small></string> + <string name="account_status_unknown">Unknown</string> + <string name="account_status_disabled">Temporarily disabled</string> + <string name="account_status_online">Online</string> + <string name="account_status_connecting">Connecting\u2026</string> + <string name="account_status_offline">Offline</string> + <string name="account_status_unauthorized">Unauthorized</string> + <string name="account_status_not_found">Server not found</string> + <string name="account_status_no_internet">No connectivity</string> + <string name="account_status_regis_fail">Registration failed</string> + <string name="account_status_regis_conflict">Username already in use</string> + <string name="account_status_regis_success">Registration completed</string> + <string name="account_status_regis_not_sup">Server does not support registration</string> + <string name="account_status_security_error">Security error</string> + <string name="account_status_incompatible_server">Incompatible server</string> + <string name="encryption_choice_unencrypted">Unencrypted</string> + <string name="encryption_choice_otr">OTR</string> + <string name="encryption_choice_pgp">OpenPGP</string> + <string name="encryption_choice_omemo">OMEMO</string> + <string name="mgmt_account_edit">Edit account</string> + <string name="mgmt_account_delete">Delete account</string> + <string name="mgmt_account_disable">Temporarily disable</string> + <string name="mgmt_account_publish_avatar">Publish avatar</string> + <string name="mgmt_account_publish_pgp">Publish OpenPGP public key</string> + <string name="mgmt_account_enable">Enable account</string> + <string name="mgmt_account_are_you_sure">Are you sure?</string> + <string name="mgmt_account_delete_confirm_text">If you delete your account your entire conversation history will be lost</string> + <string name="attach_record_voice">Record voice</string> + <string name="account_settings_jabber_id">Jabber ID</string> + <string name="account_settings_password">Password</string> + <string name="account_settings_example_jabber_id">username@example.com</string> + <string name="account_settings_confirm_password">Confirm password</string> + <string name="password">Password</string> + <string name="confirm_password">Confirm password</string> + <string name="passwords_do_not_match">Passwords do not match</string> + <string name="invalid_jid">This is not a valid Jabber ID</string> + <string name="error_out_of_memory">Out of memory. Image is too large</string> + <string name="add_phone_book_text">Do you want to add %s to your address book?</string> + <string name="contact_status_online">online</string> + <string name="contact_status_free_to_chat">free to chat</string> + <string name="contact_status_away">away</string> + <string name="contact_status_extended_away">extended away</string> + <string name="contact_status_do_not_disturb">do not disturb</string> + <string name="contact_status_offline">offline</string> + <string name="muc_details_conference">Conference</string> + <string name="muc_details_other_members">Other Members</string> + <string name="server_info_show_more">Server info</string> + <string name="server_info_mam">XEP-0313: MAM</string> + <string name="server_info_carbon_messages">XEP-0280: Message Carbons</string> + <string name="server_info_csi">XEP-0352: Client State Indication</string> + <string name="server_info_blocking">XEP-0191: Blocking Command</string> + <string name="server_info_roster_version">XEP-0237: Roster Versioning</string> + <string name="server_info_stream_management">XEP-0198: Stream Management</string> + <string name="server_info_pep">XEP-0163: PEP (Avatars / OMEMO)</string> + <string name="server_info_http_upload">XEP-0363: HTTP File Upload</string> + <string name="server_info_push">XEP-0357: Push</string> + <string name="server_info_available">available</string> + <string name="server_info_unavailable">unavailable</string> + <string name="missing_public_keys">Missing public key announcements</string> + <string name="last_seen_now">last seen just now</string> + <string name="last_seen_min">last seen 1 minute ago</string> + <string name="last_seen_mins">last seen %d minutes ago</string> + <string name="last_seen_hour">last seen 1 hour ago</string> + <string name="last_seen_hours">last seen %d hours ago</string> + <string name="last_seen_day">last seen 1 day ago</string> + <string name="last_seen_days">last seen %d days ago</string> + <string name="never_seen">never seen</string> + <string name="install_openkeychain">Encrypted message. Please install OpenKeychain to decrypt.</string> + <string name="unknown_otr_fingerprint">Unknown OTR fingerprint</string> + <string name="openpgp_messages_found">OpenPGP encrypted messages found</string> + <string name="reception_failed">Reception failed</string> + <string name="your_fingerprint">Your fingerprint</string> + <string name="otr_fingerprint">OTR fingerprint</string> + <string name="omemo_fingerprint">OMEMO fingerprint</string> + <string name="omemo_fingerprint_x509">v\\OMEMO fingerprint</string> + <string name="omemo_fingerprint_selected_message">OMEMO fingerprint of message</string> + <string name="omemo_fingerprint_x509_selected_message">v\\OMEMO fingerprint of message</string> + <string name="this_device_omemo_fingerprint">Own OMEMO fingerprint</string> + <string name="other_devices">Other devices</string> + <string name="trust_omemo_fingerprints">Trust OMEMO Fingerprints</string> + <string name="fetching_keys">Fetching keys…</string> + <string name="done">Done</string> + <string name="verify">Verify</string> + <string name="decrypt">Decrypt</string> + <string name="conferences">Conferences</string> + <string name="search">Search</string> + <string name="create_contact">Create Contact</string> + <string name="enter_contact">Enter Contact</string> + <string name="join_conference">Join Conference</string> + <string name="delete_contact">Delete Contact</string> + <string name="view_contact_details">View contact details</string> + <string name="block_contact">Block contact</string> + <string name="unblock_contact">Unblock contact</string> + <string name="create">Create</string> + <string name="select">Select</string> + <string name="contact_already_exists">The contact already exists</string> + <string name="join">Join</string> + <string name="conference_address">Conference address</string> + <string name="conference_address_example">room@conference.example.com</string> + <string name="save_as_bookmark">Save as bookmark</string> + <string name="delete_bookmark">Delete bookmark</string> + <string name="bookmark_already_exists">This bookmark already exists</string> + <string name="you">You</string> + <string name="action_edit_subject">Edit conference subject</string> + <string name="joining_conference">Joining conference…</string> + <string name="leave">Leave</string> + <string name="contact_added_you">Contact added you to contact list</string> + <string name="add_back">Add back</string> + <string name="contact_has_read_up_to_this_point">%s has read up to this point</string> + <string name="publish">Publish</string> + <string name="touch_to_choose_picture">Touch avatar to select picture from gallery</string> + <string name="publish_avatar_explanation">Please note: Everyone subscribed to your presence updates will be allowed to see this picture.</string> + <string name="publishing">Publishing…</string> + <string name="error_publish_avatar_server_reject">The server rejected your publication</string> + <string name="error_publish_avatar_converting">Something went wrong while converting your picture</string> + <string name="error_saving_avatar">Could not save avatar to disk</string> + <string name="or_long_press_for_default">(Or long press to bring back default)</string> + <string name="error_publish_avatar_no_server_support">Your server does not support the publication of avatars</string> + <string name="private_message">whispered</string> + <string name="private_message_to">to %s</string> + <string name="send_private_message_to">Send private message to %s</string> + <string name="connect">Connect</string> + <string name="account_already_exists">This account already exists</string> + <string name="next">Next</string> + <string name="server_info_session_established">Current session established</string> + <string name="additional_information">Additional Information</string> + <string name="skip">Skip</string> + <string name="disable_notifications">Disable notifications</string> + <string name="disable_notifications_for_this_conversation">Disable notifications for this conversation</string> + <string name="enable">Enable</string> + <string name="conference_requires_password">Conference requires password</string> + <string name="enter_password">Enter password</string> + <string name="missing_presence_updates">Missing presence updates from contact</string> + <string name="request_presence_updates">Please request presence updates from your contact first.\n\n<small>This will be used to determine what client(s) your contact is using.</small></string> + <string name="request_now">Request now</string> + <string name="delete_fingerprint">Delete Fingerprint</string> + <string name="sure_delete_fingerprint">Are you sure you would like to delete this fingerprint?</string> + <string name="ignore">Ignore</string> + <string name="without_mutual_presence_updates"><b>Warning:</b> Sending this without mutual presence updates could cause unexpected problems.\n\n<small>Go to contact details to verify your presence subscriptions.</small></string> + <string name="pref_security_settings">Security</string> + <string name="pref_force_encryption">Force end-to-end encryption</string> + <string name="pref_force_encryption_summary">Always send messages encrypted (except for conferences)</string> + <string name="pref_allow_message_correction">Allow message correction</string> + <string name="pref_allow_message_correction_summary">Allow your contacts to retroactively edit their messages</string> + <string name="pref_dont_save_encrypted">Don’t save encrypted messages</string> + <string name="pref_dont_save_encrypted_summary">Warning: This could lead to message loss</string> + <string name="pref_expert_options">Expert settings</string> + <string name="pref_expert_options_summary">Please be careful with these</string> + <string name="title_activity_about">About Conversations</string> + <string name="pref_about_conversations_summary">Build and licensing information</string> + <string name="pref_about_message" translatable="false"> + Conversations+ is the improved version of Conversations. \n\nThe extensions are designed and developed by thedevstack.de \n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -309,7 +323,12 @@ \n\nhttps://github.com/zxing/zxing\n(Apache License, Version 2.0) \n\nhttps://github.com/google/material-design-icons\n(CC BY 4.0) \n\nhttps://github.com/timroes/EnhancedListView\n(Apache License, Version 2.0) - \n\nhttps://github.com/ankushsachdeva/emojicon\n(Apache License, Version 2.0) + \n\nhttps://github.com/leolin310148/ShortcutBadger\n(Apache License, Version 2.0) + \n\nhttps://github.com/kyleduo/SwitchButton\n(Apache License, Version 2.0) + \n\nhttps://github.com/WhisperSystems/libaxolotl-java\n(GPLv3) + \n\nhttps://github.com/vinc3m1/RoundedImageView\n(Apache License, Version 2.0) + \n\nhttps://github.com/jdamcd/android-crop\n(Apache License, Version 2.0) + \n\nhttps://github.com/ankushsachdeva/emojicon\n(Apache License, Version 2.0) \n\nhttps://github.com/yukuku/ambilwarna\n(Apache License, Version 2.0) </string> <string name="title_pref_quiet_hours">Quiet Hours</string> @@ -326,13 +345,17 @@ <string name="pref_expert_options_other">Other</string> <string name="pref_conference_name">Conference name</string> <string name="pref_conference_name_summary">Use room’s subject instead of JID to identify conferences</string> + <string name="pref_autojoin">Automatically join conferences</string> + <string name="pref_autojoin_summary">Respect the autojoin flag in conference bookmarks</string> <string name="toast_message_otr_fingerprint">OTR fingerprint copied to clipboard!</string> + <string name="toast_message_omemo_fingerprint">OMEMO fingerprint copied to clipboard!</string> <string name="conference_banned">You are banned from this conference</string> <string name="conference_members_only">This conference is members only</string> <string name="conference_kicked">You have been kicked from this conference</string> + <string name="conference_shutdown">The conference was shut down</string> + <string name="conference_unknown_error">You are no longer in this conference</string> <string name="using_account">using account %s</string> <string name="checking_x">Checking %s on HTTP host</string> - <string name="image_file_deleted">The image file has been deleted</string> <string name="not_connected_try_again">You are not connected. Try again later</string> <string name="check_x_filesize">Check %s size</string> <string name="message_options">Message options</string> @@ -351,7 +374,6 @@ <string name="verify_otr">Verify OTR</string> <string name="remote_fingerprint">Remote Fingerprint</string> <string name="scan">scan</string> - <string name="or_touch_phones">(or touch phones)</string> <string name="smp">Socialist Millionaire Protocol</string> <string name="shared_secret_hint">Hint or Question</string> <string name="shared_secret_secret">Shared Secret</string> @@ -368,6 +390,9 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Keep service in foreground</string> <string name="pref_keep_foreground_service_summary">Prevents the operating system from killing your connection</string> + <string name="pref_export_logs">Export Logs</string> + <string name="pref_export_logs_summary">Write logs to SD card</string> + <string name="notification_export_logs_title">Writing logs to SD card</string> <string name="choose_file">Choose file</string> <string name="receiving_x_file">Receiving %1$s (%2$d%% completed)</string> <string name="download_x_file">Download %s</string> @@ -394,6 +419,17 @@ <string name="reset">Reset</string> <string name="account_image_description">Account avatar</string> <string name="copy_otr_clipboard_description">Copy OTR fingerprint to clipboard</string> + <string name="copy_omemo_clipboard_description">Copy OMEMO fingerprint to clipboard</string> + <string name="regenerate_omemo_key">Regenerate OMEMO key</string> + <string name="wipe_omemo_pep">Wipe other devices from PEP</string> + <string name="clear_other_devices">Clear devices</string> + <string name="clear_other_devices_desc">Are you sure you want to clear all other devices from the OMEMO announcement? The next time your devices connect, they will reannounce themselves, but they might not receive messages sent in the meantime.</string> + <string name="purge_key">Purge key</string> + <string name="purge_key_desc_part1">Are you sure you want to purge this key?</string> + <string name="purge_key_desc_part2">It will irreversibly be considered compromised, and you can never build a session with it again.</string> + <string name="error_no_keys_to_trust_server_error">There are no usable keys available for this contact.\nFetching new keys from the server has been unsuccessful. Maybe there is something wrong with your contacts server.</string> + <string name="error_no_keys_to_trust">There are no usable keys available for this contact. If you have purged any of their keys, they need to generate new ones.</string> + <string name="error_trustkeys_title">Error</string> <string name="fetching_history_from_server">Fetching history from server</string> <string name="no_more_history_on_server">No more history on server</string> <string name="updating">Updating…</string> @@ -432,8 +468,10 @@ <string name="public_conference">Publicly accessible conference</string> <string name="private_conference">Private, members only conference</string> <string name="conference_options">Conference options</string> - <string name="members_only">Private (Members only)</string> + <string name="members_only">Private, members only</string> <string name="non_anonymous">Non-anonymous</string> + <string name="moderated">Moderated</string> + <string name="you_are_not_participating">You are not participating</string> <string name="modified_conference_options">Modified conference options!</string> <string name="could_not_modify_conference_options">Could not modify conference options</string> <string name="never">Never</string> @@ -442,7 +480,7 @@ <string name="two_hours">2 hours</string> <string name="eight_hours">8 hours</string> <string name="until_further_notice">Until further notice</string> - <string name="pref_input_options">Input options</string> + <string name="pref_input_options">Input</string> <string name="pref_enter_is_send">Enter is send</string> <string name="pref_enter_is_send_summary">Use enter key to send message</string> <string name="pref_display_enter_key">Show enter key</string> @@ -461,7 +499,7 @@ <string name="offering_x_file">Offering %s</string> <string name="hide_offline">Hide offline</string> <string name="disable_account">Disable Account</string> - <string name="contact_is_typing">%s is typing...</string> + <string name="contact_is_typing">%s is typing…</string> <string name="contact_has_stopped_typing">%s has stopped typing</string> <string name="pref_chat_states">Typing notifications</string> <string name="pref_chat_states_summary">Let your contact know when you are writing a new message</string> @@ -472,7 +510,6 @@ <string name="received_location">Received location</string> <string name="title_undo_swipe_out_conversation">Conversation closed</string> <string name="title_undo_swipe_out_muc">Left conference</string> - <string name="pref_certificate_options">Certificate options</string> <string name="pref_dont_trust_system_cas_title">Don’t trust system CAs</string> <string name="pref_dont_trust_system_cas_summary">All certificates must be manually approved</string> <string name="pref_remove_trusted_certificates_title">Remove certificates</string> @@ -494,7 +531,6 @@ <string name="none">None</string> <string name="recently_used">Most recently used</string> <string name="choose_quick_action">Choose quick action</string> - <string name="file_not_found_on_remote_host">File not found on remote server</string> <string name="search_for_contacts_or_groups">Search for contacts or groups</string> <string name="pref_led_notification_color">LED notification color</string> <string name="pref_led_notification_color_summary">Change the color of the LED notification</string> @@ -545,4 +581,79 @@ <string name="pref_file_transfer_category">File Transfer</string> <string name="cplus_not_copied_to_clipboard_empty">Nothing to copy.</string> <string name="cplus_me">Me</string> + <string name="send_private_message">Send private message</string> + <string name="user_has_left_conference">%s has left the conference!</string> + <string name="username">Username</string> + <string name="username_hint">Username</string> + <string name="invalid_username">This is not a valid username</string> + <string name="conference_name">Conference name</string> + <string name="invalid_conference_name">This is not a valid conference name</string> + <string name="download_failed_server_not_found">Download failed: Server not found</string> + <string name="download_failed_file_not_found">Download failed: File not found</string> + <string name="download_failed_could_not_connect">Download failed: Could not connect to host</string> + <string name="account_status_tor_unavailable">Tor network unavailable</string> + <string name="server_info_broken">Broken</string> + <string name="pref_presence_settings">Presence</string> + <string name="pref_away_when_screen_off">Away when screen is off</string> + <string name="pref_away_when_screen_off_summary">Marks your resource as away when the screen is turned off</string> + <string name="pref_xa_on_silent_mode">Not available in silent mode</string> + <string name="pref_xa_on_silent_mode_summary">Marks your resource as not available when device is in silent mode</string> + <string name="pref_show_connection_options">Extended connection settings</string> + <string name="pref_show_connection_options_summary">Show hostname and port settings when setting up an account</string> + <string name="hostname_example">xmpp.example.com</string> + <string name="action_add_account_with_certificate">Add account with certificate</string> + <string name="unable_to_parse_certificate">Unable to parse certificate</string> + <string name="authenticate_with_certificate">Leave empty to authenticate w/ certificate</string> + <string name="mam_prefs">Archiving preferences</string> + <string name="server_side_mam_prefs">Server-side archiving preferences</string> + <string name="fetching_mam_prefs">Fetching archiving preferences. Please wait…</string> + <string name="unable_to_fetch_mam_prefs">Unable to fetch archiving preferences</string> + <string name="captcha_ocr">Captcha text</string> + <string name="captcha_required">Captcha required</string> + <string name="captcha_hint">enter the text from the image</string> + <string name="certificate_chain_is_not_trusted">Certificate chain is not trusted</string> + <string name="jid_does_not_match_certificate">Jabber ID does not match certificate</string> + <string name="action_renew_certificate">Renew certificate</string> + <string name="error_fetching_omemo_key">Error fetching OMEMO key!</string> + <string name="verified_omemo_key_with_certificate">Verified OMEMO key with certificate!</string> + <string name="device_does_not_support_certificates">Your device does not support the selection of client certificates!</string> + <string name="pref_connection_options">Connection</string> + <string name="account_settings_hostname">Hostname</string> + <string name="account_settings_port">Port</string> + <string name="not_a_valid_port">This is not a valid port number</string> + <string name="not_valid_hostname">This is not a valid hostname</string> + <string name="connected_accounts">%1$d of %2$d accounts connected</string> + <plurals name="x_messages"> + <item quantity="one">%d message</item> + <item quantity="other">%d messages</item> + </plurals> + <string name="shared_file_with_x">Shared file with %s</string> + <string name="shared_image_with_x">Shared image with %s</string> + <string name="no_storage_permission">Conversations need access to external storage</string> + <string name="sync_with_contacts">Synchronize with contacts</string> + <string name="sync_with_contacts_long">Conversations wants to match your XMPP roster with your contacts to show their full names and avatars.\n\nConversations will only read your contacts and match them locally without uploading them to your server.\n\nYou will now be asked to grant permission to access your contacts.</string> + <string name="certificate_information">Certificate Information</string> + <string name="certificate_subject">Subject</string> + <string name="certificate_issuer">Issuer</string> + <string name="certificate_cn">Common Name</string> + <string name="certificate_o">Organization</string> + <string name="certificate_sha1">SHA-1</string> + <string name="certicate_info_not_available">(Not available)</string> + <string name="certificate_not_found">No certificate found</string> + <string name="notify_on_all_messages">Notify on all messages</string> + <string name="notify_only_when_highlighted">Notify only when highlighted</string> + <string name="notify_never">Notifications disabled</string> + <string name="notify_paused">Notifications paused</string> + <string name="always">Always</string> + <string name="automatically">Automatically</string> + <string name="battery_optimizations_enabled">Battery optimizations enabled</string> + <string name="battery_optimizations_enabled_explained">Your device is doing some heavy battery optimizations on Conversations that might lead to delayed notifications or even message loss.\nIt is recommended to disable those.</string> + <string name="battery_optimizations_enabled_dialog">Your device is doing some heavy battery optimizations on Conversations that might lead to delayed notifications or even message loss.\n\nYou will now be asked to disable those.</string> + <string name="disable">Disable</string> + <string name="selection_too_large">The selected area is too large</string> + <string name="no_accounts">(No activated accounts)</string> + <string name="this_field_is_required">This field is required</string> + <string name="retry_decryption">Retry decryption</string> + <string name="pref_omemo_enabled_summary">Enable OMEMO?</string> + <string name="pref_omemo_enabled_title">Enable OMEMO</string> </resources> diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml index b98a37fc..e8572d9d 100644 --- a/src/main/res/values/styles.xml +++ b/src/main/res/values/styles.xml @@ -4,8 +4,18 @@ <item name="android:layout_height">1.5dp</item> <item name="android:background">@color/black12</item> </style> - <style name="Tag"> - + <style name="MD"> + <item name="animationVelocity">6</item> + <item name="insetBottom">16dp</item> + <item name="insetTop">16dp</item> + <item name="insetLeft">16dp</item> + <item name="insetRight">16dp</item> + <item name="measureFactor">1.4</item> + <item name="offDrawable">@drawable/switch_back_off</item> + <item name="onDrawable">@drawable/switch_back_on</item> + <item name="thumbDrawable">@drawable/switch_thumb</item> + <item name="thumb_margin">-17dp</item> + <item name="android:padding">16dp</item> </style> </resources>
\ No newline at end of file diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml index 5c67203b..0f8b95bb 100644 --- a/src/main/res/values/themes.xml +++ b/src/main/res/values/themes.xml @@ -18,6 +18,7 @@ <item name="attr/icon_download">@drawable/ic_action_download</item> <item name="attr/icon_edit">@drawable/ic_action_edit</item> <item name="attr/icon_edit_dark">@drawable/ic_action_edit_dark</item> + <item name="attr/icon_done">@drawable/ic_action_done</item> <item name="attr/icon_group">@drawable/ic_action_group</item> <item name="attr/icon_new">@drawable/ic_action_new</item> @@ -38,8 +39,8 @@ </style> <style name="ConversationsActionBar" parent="@android:style/Widget.Holo.Light.ActionBar.Solid.Inverse"> - <item name="android:background">@color/green500</item> - <item name="android:backgroundStacked">@color/green700</item> + <item name="android:background">@color/primary</item> + <item name="android:backgroundStacked">@color/primary_dark</item> <item name="android:displayOptions">showHome|homeAsUp|showTitle</item> <item name="android:icon">@android:color/transparent</item> </style> diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index 0232fe02..0d19ea5a 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -2,12 +2,12 @@ <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" > - <PreferenceCategory android:title="@string/pref_general" > + <PreferenceCategory android:title="@string/pref_general"> <CheckBoxPreference android:defaultValue="true" android:key="grant_new_contacts" android:summary="@string/pref_grant_presence_updates_summary" - android:title="@string/pref_grant_presence_updates" /> + android:title="@string/pref_grant_presence_updates"/> <ListPreference android:defaultValue="Mobile" @@ -15,8 +15,7 @@ android:entryValues="@array/resources" android:key="resource" android:summary="@string/pref_xmpp_resource_summary" - android:title="@string/pref_xmpp_resource" /> - + android:title="@string/pref_xmpp_resource"/> <ListPreference android:defaultValue="2" android:entries="@array/confirm_strings" @@ -29,7 +28,7 @@ android:defaultValue="false" android:key="chat_states" android:summary="@string/pref_chat_states_summary" - android:title="@string/pref_chat_states" /> + android:title="@string/pref_chat_states"/> <CheckBoxPreference android:defaultValue="true" android:key="parse_emoticons" @@ -92,6 +91,7 @@ android:title="@string/pref_notifications" /> <PreferenceScreen android:dependency="show_notification" + android:key="quiet_hours" android:summary="@string/pref_quiet_hours_summary" android:title="@string/title_pref_quiet_hours"> <CheckBoxPreference @@ -142,92 +142,120 @@ android:title="@string/pref_conference_notifications" /> </PreferenceScreen> </PreferenceCategory> - <PreferenceCategory android:title="@string/pref_ui_options" > + <PreferenceCategory android:title="@string/pref_ui_options"> <CheckBoxPreference android:defaultValue="true" android:key="use_subject" android:summary="@string/pref_conference_name_summary" - android:title="@string/pref_conference_name" /> + android:title="@string/pref_conference_name"/> <CheckBoxPreference android:defaultValue="false" android:key="use_larger_font" android:summary="@string/pref_use_larger_font_summary" - android:title="@string/pref_use_larger_font" /> + android:title="@string/pref_use_larger_font"/> <CheckBoxPreference android:defaultValue="false" android:key="send_button_status" android:summary="@string/pref_use_send_button_to_indicate_status_summary" - android:title="@string/pref_use_send_button_to_indicate_status" /> + android:title="@string/pref_use_send_button_to_indicate_status"/> <ListPreference - android:key="quick_action" android:defaultValue="recent" + android:dialogTitle="@string/choose_quick_action" android:entries="@array/quick_actions" android:entryValues="@array/quick_action_values" + android:key="quick_action" android:summary="@string/pref_quick_action_summary" - android:title="@string/pref_quick_action" - android:dialogTitle="@string/choose_quick_action"/> + android:title="@string/pref_quick_action"/> <CheckBoxPreference android:defaultValue="false" android:key="show_dynamic_tags" android:summary="@string/pref_show_dynamic_tags_summary" - android:title="@string/pref_show_dynamic_tags" /> + android:title="@string/pref_show_dynamic_tags"/> </PreferenceCategory> <PreferenceCategory - android:title="@string/pref_advanced_options" - android:key="advanced"> + android:key="advanced" + android:title="@string/pref_advanced_options"> <PreferenceScreen + android:key="expert" android:summary="@string/pref_expert_options_summary" - android:title="@string/pref_expert_options" - android:key="expert"> - <PreferenceCategory android:title="@string/pref_encryption_settings" > - <CheckBoxPreference - android:defaultValue="false" - android:key="force_encryption" - android:summary="@string/pref_force_encryption_summary" - android:title="@string/pref_force_encryption" /> + android:title="@string/pref_expert_options"> + <PreferenceCategory android:title="@string/pref_security_settings"> <CheckBoxPreference android:defaultValue="false" android:key="dont_save_encrypted" android:summary="@string/pref_dont_save_encrypted_summary" - android:title="@string/pref_dont_save_encrypted" /> + android:title="@string/pref_dont_save_encrypted"/> + <CheckBoxPreference + android:defaultValue="false" + android:key="dont_trust_system_cas" + android:summary="@string/pref_dont_trust_system_cas_summary" + android:title="@string/pref_dont_trust_system_cas_title"/> + <Preference + android:key="remove_trusted_certificates" + android:summary="@string/pref_remove_trusted_certificates_summary"/> + </PreferenceCategory> + <PreferenceCategory + android:key="connection_options" + android:title="@string/pref_connection_options"> + <CheckBoxPreference + android:defaultValue="false" + android:key="show_connection_options" + android:summary="@string/pref_show_connection_options_summary" + android:title="@string/pref_show_connection_options"/> + </PreferenceCategory> + <PreferenceCategory android:title="@string/pref_input_options"> + <CheckBoxPreference + android:defaultValue="false" + android:key="enter_is_send" + android:summary="@string/pref_enter_is_send_summary" + android:title="@string/pref_enter_is_send"/> + <CheckBoxPreference + android:defaultValue="false" + android:key="display_enter_key" + android:summary="@string/pref_display_enter_key_summary" + android:title="@string/pref_display_enter_key"/> </PreferenceCategory> - <PreferenceCategory android:title="@string/pref_input_options"> - <CheckBoxPreference - android:defaultValue="false" - android:key="enter_is_send" - android:title="@string/pref_enter_is_send" - android:summary="@string/pref_enter_is_send_summary" /> - <CheckBoxPreference - android:defaultValue="false" - android:key="display_enter_key" - android:title="@string/pref_display_enter_key" - android:summary="@string/pref_display_enter_key_summary" /> - </PreferenceCategory> - <PreferenceCategory android:title="@string/pref_certificate_options"> - <CheckBoxPreference - android:defaultValue="false" - android:key="dont_trust_system_cas" - android:title="@string/pref_dont_trust_system_cas_title" - android:summary="@string/pref_dont_trust_system_cas_summary" /> - <Preference - android:key="remove_trusted_certificates" - android:title="@string/pref_remove_trusted_certificates_title" - android:summary="@string/pref_remove_trusted_certificates_summary" /> - </PreferenceCategory> - <PreferenceCategory android:title="@string/pref_expert_options_other" > + <PreferenceCategory android:title="@string/pref_presence_settings"> + <CheckBoxPreference + android:defaultValue="false" + android:key="away_when_screen_off" + android:summary="@string/pref_away_when_screen_off_summary" + android:title="@string/pref_away_when_screen_off"/> + <CheckBoxPreference + android:defaultValue="false" + android:key="xa_on_silent_mode" + android:summary="@string/pref_xa_on_silent_mode_summary" + android:title="@string/pref_xa_on_silent_mode"/> + </PreferenceCategory> + <PreferenceCategory android:key="other_expert_settings" android:title="@string/pref_expert_options_other"> + <CheckBoxPreference + android:key="autojoin" + android:defaultValue="true" + android:title="@string/pref_autojoin" + android:summary="@string/pref_autojoin_summary" + /> <CheckBoxPreference android:defaultValue="false" android:key="indicate_received" android:summary="@string/pref_use_indicate_received_summary" - android:title="@string/pref_use_indicate_received" /> + android:title="@string/pref_use_indicate_received"/> <CheckBoxPreference android:defaultValue="false" android:key="keep_foreground_service" - android:title="@string/pref_keep_foreground_service" - android:summary="@string/pref_keep_foreground_service_summary" /> + android:summary="@string/pref_keep_foreground_service_summary" + android:title="@string/pref_keep_foreground_service"/> + <de.thedevstack.conversationsplus.ui.ExportLogsPreference + android:key="export_logs" + android:summary="@string/pref_export_logs_summary" + android:title="@string/pref_export_logs"/> <de.thedevstack.conversationsplus.ui.preferences.LogInformationPreference android:summary="@string/pref_show_logcat_summary" android:title="@string/pref_show_logcat_title"/> + <CheckBoxPreference + android:defaultValue="@bool/omemo_enabled" + android:key="omemo_enabled" + android:summary="@string/pref_omemo_enabled_summary" + android:title="@string/pref_omemo_enabled_title"/> </PreferenceCategory> </PreferenceScreen> @@ -235,9 +263,9 @@ android:defaultValue="false" android:key="never_send" android:summary="@string/pref_never_send_crash_summary" - android:title="@string/pref_never_send_crash" /> + android:title="@string/pref_never_send_crash"/> </PreferenceCategory> - <de.thedevstack.conversationsplus.ui.AboutPreference - android:summary="@string/pref_about_conversations_summary" - android:title="@string/title_activity_about" /> + <de.thedevstack.conversationsplus.ui.AboutPreference + android:summary="@string/pref_about_conversations_summary" + android:title="@string/title_activity_about"/> </PreferenceScreen> diff --git a/src/playstore/AndroidManifest.xml b/src/playstore/AndroidManifest.xml new file mode 100644 index 00000000..a1b91be3 --- /dev/null +++ b/src/playstore/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest + package="eu.siacs.conversations" + xmlns:android="http://schemas.android.com/apk/res/android"> + + <permission android:name="eu.siacs.conversations.permission.C2D_MESSAGE" + android:protectionLevel="signature"/> + <uses-permission android:name="eu.siacs.conversations.permission.C2D_MESSAGE"/> + + <application> + + <receiver + android:name="com.google.android.gms.gcm.GcmReceiver" + android:exported="true" + android:permission="com.google.android.c2dm.permission.SEND" > + <intent-filter> + <action android:name="com.google.android.c2dm.intent.RECEIVE" /> + <category android:name="com.example.gcm" /> + </intent-filter> + </receiver> + <service + android:name=".services.PushMessageReceiver" + android:exported="false" > + <intent-filter> + <action android:name="com.google.android.c2dm.intent.RECEIVE" /> + </intent-filter> + </service> + + <service android:name=".services.InstanceIdService" android:exported="false"> + <intent-filter> + <action android:name="com.google.android.gms.iid.InstanceID"/> + </intent-filter> + </service> + </application> +</manifest> diff --git a/src/playstore/java/eu/siacs/conversations/services/InstanceIdService.java b/src/playstore/java/eu/siacs/conversations/services/InstanceIdService.java new file mode 100644 index 00000000..dc080430 --- /dev/null +++ b/src/playstore/java/eu/siacs/conversations/services/InstanceIdService.java @@ -0,0 +1,15 @@ +package eu.siacs.conversations.services; + +import android.content.Intent; + +import com.google.android.gms.iid.InstanceIDListenerService; + +public class InstanceIdService extends InstanceIDListenerService { + + @Override + public void onTokenRefresh() { + Intent intent = new Intent(this, XmppConnectionService.class); + intent.setAction(XmppConnectionService.ACTION_GCM_TOKEN_REFRESH); + startService(intent); + } +} diff --git a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java new file mode 100644 index 00000000..3fdaf832 --- /dev/null +++ b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java @@ -0,0 +1,119 @@ +package eu.siacs.conversations.services; + +import android.provider.Settings; +import android.util.Log; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; +import com.google.android.gms.gcm.GoogleCloudMessaging; +import com.google.android.gms.iid.InstanceID; + +import java.io.IOException; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.OnIqPacketReceived; +import eu.siacs.conversations.xmpp.XmppConnection; +import eu.siacs.conversations.xmpp.forms.Data; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.stanzas.IqPacket; + +public class PushManagementService { + + private static final String APP_SERVER = "push.siacs.eu"; + + protected final XmppConnectionService mXmppConnectionService; + + public PushManagementService(XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + public void registerPushTokenOnServer(final Account account) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": has push support"); + retrieveGcmInstanceToken(new OnGcmInstanceTokenRetrieved() { + @Override + public void onGcmInstanceTokenRetrieved(String token) { + try { + final String deviceId = Settings.Secure.getString(mXmppConnectionService.getContentResolver(), Settings.Secure.ANDROID_ID); + IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(Jid.fromString(APP_SERVER), token, deviceId); + mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Element command = packet.findChild("command","http://jabber.org/protocol/commands"); + if (packet.getType() == IqPacket.TYPE.RESULT && command != null) { + Element x = command.findChild("x","jabber:x:data"); + if (x != null) { + Data data = Data.parse(x); + try { + String node = data.getValue("node"); + String secret = data.getValue("secret"); + Jid jid = Jid.fromString(data.getValue("jid")); + if (node != null && secret != null) { + enablePushOnServer(account, jid, node, secret); + } + } catch (InvalidJidException e) { + e.printStackTrace(); + } + } + } else { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": invalid response from app server"); + } + } + }); + } catch (InvalidJidException ignored) { + + } + } + }); + } + + private void enablePushOnServer(final Account account, final Jid jid, final String node, final String secret) { + IqPacket enable = mXmppConnectionService.getIqGenerator().enablePush(jid, node, secret); + mXmppConnectionService.sendIqPacket(account, enable, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": successfully enabled push on server"); + } else if (packet.getType() == IqPacket.TYPE.ERROR) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": enabling push on server failed"); + } + } + }); + } + + private void retrieveGcmInstanceToken(final OnGcmInstanceTokenRetrieved instanceTokenRetrieved) { + new Thread(new Runnable() { + @Override + public void run() { + InstanceID instanceID = InstanceID.getInstance(mXmppConnectionService); + try { + String token = instanceID.getToken(mXmppConnectionService.getString(R.string.gcm_defaultSenderId), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); + instanceTokenRetrieved.onGcmInstanceTokenRetrieved(token); + } catch (IOException e) { + } + } + }).start(); + + } + + + public boolean available(Account account) { + final XmppConnection connection = account.getXmppConnection(); + return connection != null && connection.getFeatures().push() && playServicesAvailable(); + } + + private boolean playServicesAvailable() { + return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(mXmppConnectionService) == ConnectionResult.SUCCESS; + } + + public boolean isStub() { + return false; + } + + interface OnGcmInstanceTokenRetrieved { + void onGcmInstanceTokenRetrieved(String token); + } +} diff --git a/src/playstore/java/eu/siacs/conversations/services/PushMessageReceiver.java b/src/playstore/java/eu/siacs/conversations/services/PushMessageReceiver.java new file mode 100644 index 00000000..37c95e13 --- /dev/null +++ b/src/playstore/java/eu/siacs/conversations/services/PushMessageReceiver.java @@ -0,0 +1,20 @@ +package eu.siacs.conversations.services; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +import com.google.android.gms.gcm.GcmListenerService; + +import eu.siacs.conversations.Config; + +public class PushMessageReceiver extends GcmListenerService { + + @Override + public void onMessageReceived(String from, Bundle data) { + Intent intent = new Intent(this, XmppConnectionService.class); + intent.setAction(XmppConnectionService.ACTION_GCM_MESSAGE_RECEIVED); + intent.replaceExtras(data); + startService(intent); + } +} |