diff options
Diffstat (limited to 'src')
148 files changed, 3982 insertions, 1640 deletions
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 56ed5c76..d6991f88 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -197,13 +197,18 @@ <provider android:name="android.support.v4.content.FileProvider" - android:authorities="eu.siacs.conversations.files" + android:authorities="${applicationId}.files" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> + <provider + android:authorities="${applicationId}.barcodes" + android:name=".services.BarcodeProvider" + android:exported="false" + android:grantUriPermissions="true"/> </application> diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 28496b37..ee3db65b 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -54,17 +54,13 @@ public final class Config { public static final int PING_MAX_INTERVAL = 300; public static final int IDLE_PING_INTERVAL = 600; //540 is minimum according to docs; public static final int PING_MIN_INTERVAL = 30; + public static final int LOW_PING_TIMEOUT = 1; // used after push received 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 CONNECT_DISCO_TIMEOUT = 20; public static final int MINI_GRACE_PERIOD = 750; - public static final boolean PUSH_MODE = false; //closes the tcp connection when going to background - //and after PING_MIN_INTERVAL of inactivity - //very experimental. only enable this if you want - //to around with GCM push - public static final int AVATAR_SIZE = 192; public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP; @@ -82,6 +78,10 @@ public final class Config { public static final int MAX_DISPLAY_MESSAGE_CHARS = 4096; + public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; + + public static final long OMEMO_AUTO_EXPIRY = 7 * MILLISECONDS_IN_DAY; + 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 @@ -97,16 +97,18 @@ public final class Config { public static final boolean X509_VERIFICATION = false; //use x509 certificates to verify OMEMO keys - public static final boolean ONLY_INTERNAL_STORAGE = false; //use internal storage instead of sdcard to save attachments + public static final boolean ONLY_INTERNAL_STORAGE = true; //use internal storage instead of sdcard to save attachments public static final boolean IGNORE_ID_REWRITE_IN_MUC = true; public static final boolean PARSE_REAL_JID_FROM_MUC_MAM = false; //dangerous if server doesn’t filter - 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; + public static final long FREQUENT_RESTARTS_DETECTION_WINDOW = 12 * 60 * 60 * 1000; // 10 hours + public static final long FREQUENT_RESTARTS_THRESHOLD = 16; + public static final ChatState DEFAULT_CHATSTATE = ChatState.ACTIVE; public static final int TYPING_TIMEOUT = 8; diff --git a/src/main/java/eu/siacs/conversations/OmemoActivity.java b/src/main/java/eu/siacs/conversations/OmemoActivity.java new file mode 100644 index 00000000..c0c7b298 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/OmemoActivity.java @@ -0,0 +1,290 @@ +package eu.siacs.conversations; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.MenuItem; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; + +import java.security.cert.X509Certificate; +import java.util.Arrays; + +import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.ui.TrustKeysActivity; +import eu.siacs.conversations.ui.XmppActivity; +import eu.siacs.conversations.ui.widget.Switch; +import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.XmppUri; + + +public abstract class OmemoActivity extends XmppActivity { + + private Account mSelectedAccount; + private String mSelectedFingerprint; + + protected XmppUri mPendingFingerprintVerificationUri = null; + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu,v,menuInfo); + Object account = v.getTag(R.id.TAG_ACCOUNT); + Object fingerprint = v.getTag(R.id.TAG_FINGERPRINT); + Object fingerprintStatus = v.getTag(R.id.TAG_FINGERPRINT_STATUS);; + if (account != null + && fingerprint != null + && account instanceof Account + && fingerprintStatus != null + && fingerprint instanceof String + && fingerprintStatus instanceof FingerprintStatus) { + getMenuInflater().inflate(R.menu.omemo_key_context, menu); + MenuItem purgeItem = menu.findItem(R.id.purge_omemo_key); + MenuItem verifyScan = menu.findItem(R.id.verify_scan); + if (this instanceof TrustKeysActivity) { + purgeItem.setVisible(false); + verifyScan.setVisible(false); + } else { + FingerprintStatus status = (FingerprintStatus) fingerprintStatus; + if (!status.isActive() || status.isVerified()) { + verifyScan.setVisible(false); + } + } + this.mSelectedAccount = (Account) account; + this.mSelectedFingerprint = (String) fingerprint; + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.purge_omemo_key: + showPurgeKeyDialog(mSelectedAccount,mSelectedFingerprint); + break; + case R.id.copy_omemo_key: + copyOmemoFingerprint(mSelectedFingerprint); + break; + case R.id.verify_scan: + new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE")); + break; + } + return true; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); + if (scanResult != null && scanResult.getFormatName() != null) { + String data = scanResult.getContents(); + XmppUri uri = new XmppUri(data); + if (xmppConnectionServiceBound) { + processFingerprintVerification(uri); + } else { + this.mPendingFingerprintVerificationUri =uri; + } + } + } + + protected abstract void processFingerprintVerification(XmppUri uri); + + protected void copyOmemoFingerprint(String fingerprint) { + if (copyTextToClipboard(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)), R.string.omemo_fingerprint)) { + Toast.makeText( + this, + R.string.toast_message_omemo_fingerprint, + Toast.LENGTH_SHORT).show(); + } + } + + protected void addFingerprintRow(LinearLayout keys, final XmppAxolotlSession session, boolean highlight) { + final Account account = session.getAccount(); + final String fingerprint = session.getFingerprint(); + addFingerprintRowWithListeners(keys, + session.getAccount(), + session.getFingerprint(), + highlight, + session.getTrust(), + true, + true, + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + account.getAxolotlService().setFingerprintTrust(fingerprint, FingerprintStatus.createActive(isChecked)); + } + }); + } + + protected void addFingerprintRowWithListeners(LinearLayout keys, final Account account, + final String fingerprint, + boolean highlight, + FingerprintStatus status, + boolean showTag, + boolean undecidedNeedEnablement, + CompoundButton.OnCheckedChangeListener + onCheckedChangeListener) { + View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false); + TextView key = (TextView) view.findViewById(R.id.key); + TextView keyType = (TextView) view.findViewById(R.id.key_type); + if (Config.X509_VERIFICATION && status.getTrust() == FingerprintStatus.Trust.VERIFIED_X509) { + View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(View v) { + showX509Certificate(account,fingerprint); + } + }; + key.setOnClickListener(listener); + keyType.setOnClickListener(listener); + } + Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust); + ImageView verifiedFingerprintSymbol = (ImageView) view.findViewById(R.id.verified_fingerprint); + trustToggle.setVisibility(View.VISIBLE); + registerForContextMenu(view); + view.setTag(R.id.TAG_ACCOUNT,account); + view.setTag(R.id.TAG_FINGERPRINT,fingerprint); + view.setTag(R.id.TAG_FINGERPRINT_STATUS,status); + boolean x509 = Config.X509_VERIFICATION && status.getTrust() == FingerprintStatus.Trust.VERIFIED_X509; + final View.OnClickListener toast; + trustToggle.setChecked(status.isTrusted(), false); + + if (status.isActive()){ + key.setTextColor(getPrimaryTextColor()); + keyType.setTextColor(getSecondaryTextColor()); + if (status.isVerified()) { + verifiedFingerprintSymbol.setVisibility(View.VISIBLE); + verifiedFingerprintSymbol.setAlpha(1.0f); + trustToggle.setVisibility(View.GONE); + verifiedFingerprintSymbol.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + replaceToast(getString(R.string.this_device_has_been_verified), false); + } + }); + toast = null; + } else { + verifiedFingerprintSymbol.setVisibility(View.GONE); + trustToggle.setVisibility(View.VISIBLE); + trustToggle.setOnCheckedChangeListener(onCheckedChangeListener); + if (status.getTrust() == FingerprintStatus.Trust.UNDECIDED && undecidedNeedEnablement) { + trustToggle.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + account.getAxolotlService().setFingerprintTrust(fingerprint,FingerprintStatus.createActive(false)); + v.setEnabled(true); + v.setOnClickListener(null); + } + }); + trustToggle.setEnabled(false); + } else { + trustToggle.setOnClickListener(null); + trustToggle.setEnabled(true); + } + toast = new View.OnClickListener() { + @Override + public void onClick(View v) { + hideToast(); + } + }; + } + } else { + key.setTextColor(getTertiaryTextColor()); + keyType.setTextColor(getTertiaryTextColor()); + toast = new View.OnClickListener() { + @Override + public void onClick(View v) { + replaceToast(getString(R.string.this_device_is_no_longer_in_use), false); + } + }; + if (status.isVerified()) { + trustToggle.setVisibility(View.GONE); + verifiedFingerprintSymbol.setVisibility(View.VISIBLE); + verifiedFingerprintSymbol.setAlpha(0.4368f); + verifiedFingerprintSymbol.setOnClickListener(toast); + } else { + trustToggle.setVisibility(View.VISIBLE); + verifiedFingerprintSymbol.setVisibility(View.GONE); + trustToggle.setOnClickListener(null); + trustToggle.setEnabled(false); + trustToggle.setOnClickListener(toast); + } + } + + view.setOnClickListener(toast); + key.setOnClickListener(toast); + keyType.setOnClickListener(toast); + 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); + } + + public void showPurgeKeyDialog(final Account account, final String fingerprint) { + AlertDialog.Builder builder = new AlertDialog.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.purge_key), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + account.getAxolotlService().purgeKey(fingerprint); + refreshUi(); + } + }); + builder.create().show(); + } + + private void showX509Certificate(Account account, String fingerprint) { + 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(); + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java index 63f846c5..87b58613 100644 --- a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java +++ b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java @@ -134,9 +134,10 @@ public class PgpDecryptionService { break; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: synchronized (PgpDecryptionService.this) { + PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); messages.addFirst(message); currentMessage = null; - storePendingIntent((PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT)); + storePendingIntent(pendingIntent); } break; case OpenPgpApi.RESULT_CODE_ERROR: @@ -164,9 +165,10 @@ public class PgpDecryptionService { break; case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: synchronized (PgpDecryptionService.this) { + PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT); messages.addFirst(message); currentMessage = null; - storePendingIntent((PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT)); + storePendingIntent(pendingIntent); } break; case OpenPgpApi.RESULT_CODE_ERROR: diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java index da315812..1a5367fd 100644 --- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java +++ b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java @@ -259,8 +259,13 @@ public class PgpEngine { account); return; case OpenPgpApi.RESULT_CODE_ERROR: - logError(account, (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)); - callback.error(R.string.unable_to_connect_to_keychain, account); + OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR); + if (error != null && "signing subkey not found!".equals(error.getMessage())) { + callback.error(0,account); + } else { + logError(account, error); + callback.error(R.string.unable_to_connect_to_keychain, null); + } } } }); diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 1bc5fa83..09beb22c 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -25,13 +25,17 @@ import java.security.PrivateKey; import java.security.Security; import java.security.Signature; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; 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 java.util.concurrent.atomic.AtomicBoolean; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; @@ -73,6 +77,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { private int numPublishTriesOnEmptyPep = 0; private boolean pepBroken = false; + private AtomicBoolean ownPushPending = new AtomicBoolean(false); + @Override public void onAdvancedStreamFeaturesAvailable(Account account) { if (Config.supportOmemo() @@ -88,7 +94,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { for(Jid jid : jids) { if (deviceIds.get(jid) != null) { for (Integer foreignId : this.deviceIds.get(jid)) { - AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId); + AxolotlAddress address = new AxolotlAddress(jid.toPreppedString(), foreignId); if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) { return true; } @@ -98,6 +104,23 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return false; } + public void preVerifyFingerprint(Contact contact, String fingerprint) { + axolotlStore.preVerifyFingerprint(contact.getAccount(), contact.getJid().toBareJid().toPreppedString(), fingerprint); + } + + public void preVerifyFingerprint(Account account, String fingerprint) { + axolotlStore.preVerifyFingerprint(account, account.getJid().toBareJid().toPreppedString(), fingerprint); + } + + public boolean hasVerifiedKeys(String name) { + for(XmppAxolotlSession session : this.sessions.getAll(new AxolotlAddress(name,0)).values()) { + if (session.getTrust().isVerified()) { + return true; + } + } + return false; + } + private static class AxolotlAddressMap<T> { protected Map<String, Map<Integer, T>> map; protected final Object MAP_LOCK = new Object(); @@ -164,7 +187,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) { for (Integer deviceId : deviceIds) { AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId); - Log.d(Config.LOGTAG, AxolotlService.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", "")); @@ -185,8 +207,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } private void fillMap(SQLiteAxolotlStore store) { - List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toString()); - putDevicesForJid(account.getJid().toBareJid().toString(), deviceIds, store); + List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toPreppedString()); + putDevicesForJid(account.getJid().toBareJid().toPreppedString(), deviceIds, store); for (Contact contact : account.getRoster().getContacts()) { Jid bareJid = contact.getJid().toBareJid(); String address = bareJid.toString(); @@ -200,7 +222,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { public void put(AxolotlAddress address, XmppAxolotlSession value) { super.put(address, value); value.setNotFresh(); - xmppConnectionService.syncRosterToDisk(account); + xmppConnectionService.syncRosterToDisk(account); //TODO why? } public void put(XmppAxolotlSession session) { @@ -213,6 +235,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { SUCCESS, SUCCESS_VERIFIED, TIMEOUT, + SUCCESS_TRUSTED, ERROR } @@ -220,7 +243,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { public void clearErrorFor(Jid jid) { synchronized (MAP_LOCK) { - Map<Integer, FetchStatus> devices = this.map.get(jid.toBareJid().toString()); + Map<Integer, FetchStatus> devices = this.map.get(jid.toBareJid().toPreppedString()); if (devices == null) { return; } @@ -256,29 +279,29 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", ""); } - public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) { - return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust); + public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status) { + return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toPreppedString(), status); } - public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Jid jid) { - return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toString(), trust); + public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, Jid jid) { + return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toPreppedString(), status); } - public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, List<Jid> jids) { + public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, List<Jid> jids) { Set<IdentityKey> keys = new HashSet<>(); for(Jid jid : jids) { - keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toString(), trust)); + keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toPreppedString(), status)); } return keys; } public long getNumTrustedKeys(Jid jid) { - return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString()); + return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()); } public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) { for(Jid jid : jids) { - if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString()) == 0) { + if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()) == 0) { return true; } } @@ -286,17 +309,23 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } private AxolotlAddress getAddressForJid(Jid jid) { - return new AxolotlAddress(jid.toString(), 0); + return new AxolotlAddress(jid.toPreppedString(), 0); } - private Set<XmppAxolotlSession> findOwnSessions() { + public Collection<XmppAxolotlSession> findOwnSessions() { AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid()); - return new HashSet<>(this.sessions.getAll(ownAddress).values()); + ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(ownAddress).values()); + Collections.sort(s); + return s; } - private Set<XmppAxolotlSession> findSessionsForContact(Contact contact) { + + + public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) { AxolotlAddress contactAddress = getAddressForJid(contact.getJid()); - return new HashSet<>(this.sessions.getAll(contactAddress).values()); + ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(contactAddress).values()); + Collections.sort(s); + return s; } private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) { @@ -307,22 +336,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return sessions; } - public Set<String> getFingerprintsForOwnSessions() { - Set<String> fingerprints = new HashSet<>(); - for (XmppAxolotlSession session : findOwnSessions()) { - fingerprints.add(session.getFingerprint()); - } - return fingerprints; - } - - 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(Jid jid) { return sessions.hasAny(getAddressForJid(jid)); } @@ -355,58 +368,54 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { 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); + public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) { + boolean me = jid.toBareJid().equals(account.getJid().toBareJid()); + if (me && ownPushPending.getAndSet(false)) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": ignoring own device update because of pending push"); + return; + } + boolean needsPublishing = me && !deviceIds.contains(getOwnDeviceId()); + if (me) { + deviceIds.remove(getOwnDeviceId()); + } + Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toPreppedString())); + expiredDevices.removeAll(deviceIds); + for (Integer deviceId : expiredDevices) { + AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId); XmppAxolotlSession session = sessions.get(address); - if (session != null && session.getFingerprint() != null - && session.getTrust() == from) { - session.setTrust(to); + if (session != null && session.getFingerprint() != null) { + if (session.getTrust().isActive()) { + session.setTrust(session.getTrust().toInactive()); + } } } - } - - 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 attempts and pepBroken status."); - pepBroken = false; - numPublishTriesOnEmptyPep = 0; + Set<Integer> newDevices = new HashSet<>(deviceIds); + for (Integer deviceId : newDevices) { + AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId); + XmppAxolotlSession session = sessions.get(address); + if (session != null && session.getFingerprint() != null) { + if (!session.getTrust().isActive()) { + Log.d(Config.LOGTAG,"reactivating device with fingprint "+session.getFingerprint()); + session.setTrust(session.getTrust().toActive()); + } } - if (deviceIds.contains(getOwnDeviceId())) { - deviceIds.remove(getOwnDeviceId()); - } else { - publishOwnDeviceId(deviceIds); + } + if (me) { + if (Config.OMEMO_AUTO_EXPIRY != 0) { + needsPublishing |= deviceIds.removeAll(getExpiredDevices()); } for (Integer deviceId : deviceIds) { - AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId); + AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId); if (sessions.get(ownDeviceAddress) == null) { buildSessionFromPEP(ownDeviceAddress); } } + if (needsPublishing) { + publishOwnDeviceId(deviceIds); + } } - 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.updateConversationUi(); //update the lock icon mXmppConnectionService.keyStatusUpdated(null); } @@ -419,16 +428,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { deviceIds.add(getOwnDeviceId()); IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); Log.d(Config.LOGTAG, AxolotlService.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! - } - }); + mXmppConnectionService.sendIqPacket(account, publish, null); } public void purgeKey(final String fingerprint) { - axolotlStore.setFingerprintTrust(fingerprint.replaceAll("\\s", ""), XmppAxolotlSession.Trust.COMPROMISED); + axolotlStore.setFingerprintStatus(fingerprint.replaceAll("\\s", ""), FingerprintStatus.createCompromised()); } public void publishOwnDeviceIdIfNeeded() { @@ -445,14 +449,30 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } else { Element item = mXmppConnectionService.getIqParser().getItem(packet); Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); - if (!deviceIds.contains(getOwnDeviceId())) { - publishOwnDeviceId(deviceIds); - } + registerDevices(account.getJid().toBareJid(),deviceIds); } } }); } + private Set<Integer> getExpiredDevices() { + Set<Integer> devices = new HashSet<>(); + for(XmppAxolotlSession session : findOwnSessions()) { + if (session.getTrust().isActive()) { + long diff = System.currentTimeMillis() - session.getTrust().getLastActivation(); + if (diff > Config.OMEMO_AUTO_EXPIRY) { + long lastMessageDiff = System.currentTimeMillis() - mXmppConnectionService.databaseBackend.getLastTimeFingerprintUsed(account,session.getFingerprint()); + if (lastMessageDiff > Config.OMEMO_AUTO_EXPIRY) { + devices.add(session.getRemoteAddress().getDeviceId()); + session.setTrust(session.getTrust().toInactive()); + Log.d(Config.LOGTAG, "added own device " + session.getFingerprint() + " to list of expired devices. Last message received "+(lastMessageDiff/1000)+"s ago"); + } + } + } + } + return devices; + } + public void publishOwnDeviceId(Set<Integer> deviceIds) { Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds); if (!deviceIdsCopy.contains(getOwnDeviceId())) { @@ -471,9 +491,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } deviceIdsCopy.add(getOwnDeviceId()); IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy); + ownPushPending.set(true); mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { + ownPushPending.set(false); if (packet.getType() == IqPacket.TYPE.ERROR) { pepBroken = true; Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error")); @@ -692,16 +714,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return jids; } - public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) { - return axolotlStore.getFingerprintTrust(fingerprint); + public FingerprintStatus getFingerprintTrust(String fingerprint) { + return axolotlStore.getFingerprintStatus(fingerprint); } public X509Certificate getFingerprintCertificate(String fingerprint) { return axolotlStore.getFingerprintCertificate(fingerprint); } - public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) { - axolotlStore.setFingerprintTrust(fingerprint, trust); + public void setFingerprintTrust(String fingerprint, FingerprintStatus status) { + axolotlStore.setFingerprintStatus(fingerprint, status); } private void verifySessionWithPEP(final XmppAxolotlSession session) { @@ -724,7 +746,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { 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); + setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true)); axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]); fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED); Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]); @@ -759,15 +781,18 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } 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)) { + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0); + Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress); + Map<Integer, FetchStatus> remote = fetchStatusMap.getAll(address); + if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) { FetchStatus report = null; - if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.SUCCESS_VERIFIED) - | fetchStatusMap.getAll(address).containsValue(FetchStatus.SUCCESS_VERIFIED)) { + if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) { + report = FetchStatus.SUCCESS; + } else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) { report = FetchStatus.SUCCESS_VERIFIED; - } else if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.ERROR) - || fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) { + } else if (own.containsValue(FetchStatus.SUCCESS_TRUSTED) || remote.containsValue(FetchStatus.SUCCESS_TRUSTED)) { + report = FetchStatus.SUCCESS_TRUSTED; + } else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) { report = FetchStatus.ERROR; } mXmppConnectionService.keyStatusUpdated(report); @@ -823,7 +848,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { if (Config.X509_VERIFICATION) { verifySessionWithPEP(session); } else { - fetchStatusMap.put(address, FetchStatus.SUCCESS); + FingerprintStatus status = getFingerprintTrust(bundle.getIdentityKey().getFingerprint().replaceAll("\\s","")); + FetchStatus fetchStatus; + if (status != null && status.isVerified()) { + fetchStatus = FetchStatus.SUCCESS_VERIFIED; + } else if (status != null && status.isTrusted()) { + fetchStatus = FetchStatus.SUCCESS_TRUSTED; + } else { + fetchStatus = FetchStatus.SUCCESS; + } + fetchStatusMap.put(address, fetchStatus); finishBuildingSessionsFromPEP(address); } } catch (UntrustedIdentityException | InvalidKeyException e) { @@ -850,7 +884,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid); if (deviceIds.get(jid) != null) { for (Integer foreignId : this.deviceIds.get(jid)) { - AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId); + AxolotlAddress address = new AxolotlAddress(jid.toPreppedString(), foreignId); if (sessions.get(address) == null) { IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); if (identityKey != null) { @@ -873,7 +907,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } 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); + AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownId); if (sessions.get(address) == null) { IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); if (identityKey != null) { @@ -921,8 +955,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { sessions.addAll(findOwnSessions()); boolean verified = false; for(XmppAxolotlSession session : sessions) { - if (session.getTrust().trusted()) { - if (session.getTrust() == XmppAxolotlSession.Trust.TRUSTED_X509) { + if (session.getTrust().isTrustedAndActive()) { + if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) { verified = true; } else { return false; @@ -933,12 +967,12 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } public boolean hasPendingKeyFetches(Account account, List<Jid> jids) { - AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0); + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0); if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) { return true; } for(Jid jid : jids) { - AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toString(), 0); + AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), 0); if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) { return true; } @@ -952,18 +986,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { account.getJid().toBareJid(), getOwnDeviceId()); Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation); - Set<XmppAxolotlSession> ownSessions = findOwnSessions(); + Collection<XmppAxolotlSession> ownSessions = findOwnSessions(); if (remoteSessions.isEmpty()) { return null; } - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements..."); for (XmppAxolotlSession session : remoteSessions) { - Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); axolotlMessage.addDevice(session); } - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements..."); for (XmppAxolotlSession session : ownSessions) { - Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); axolotlMessage.addDevice(session); } @@ -1038,7 +1068,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) { - AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(), + AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toPreppedString(), message.getSenderDeviceId()); XmppAxolotlSession session = sessions.get(senderAddress); if (session == null) { diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java new file mode 100644 index 00000000..31b2264b --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java @@ -0,0 +1,180 @@ +package eu.siacs.conversations.crypto.axolotl; + +import android.content.ContentValues; +import android.database.Cursor; + +public class FingerprintStatus implements Comparable<FingerprintStatus> { + + private static final long DO_NOT_OVERWRITE = -1; + + private Trust trust = Trust.UNTRUSTED; + private boolean active = false; + private long lastActivation = DO_NOT_OVERWRITE; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FingerprintStatus that = (FingerprintStatus) o; + + return active == that.active && trust == that.trust; + } + + @Override + public int hashCode() { + int result = trust.hashCode(); + result = 31 * result + (active ? 1 : 0); + return result; + } + + private FingerprintStatus() { + + + } + + public ContentValues toContentValues() { + final ContentValues contentValues = new ContentValues(); + contentValues.put(SQLiteAxolotlStore.TRUST,trust.toString()); + contentValues.put(SQLiteAxolotlStore.ACTIVE,active ? 1 : 0); + if (lastActivation != DO_NOT_OVERWRITE) { + contentValues.put(SQLiteAxolotlStore.LAST_ACTIVATION,lastActivation); + } + return contentValues; + } + + public static FingerprintStatus fromCursor(Cursor cursor) { + final FingerprintStatus status = new FingerprintStatus(); + try { + status.trust = Trust.valueOf(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.TRUST))); + } catch(IllegalArgumentException e) { + status.trust = Trust.UNTRUSTED; + } + status.active = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.ACTIVE)) > 0; + status.lastActivation = cursor.getLong(cursor.getColumnIndex(SQLiteAxolotlStore.LAST_ACTIVATION)); + return status; + } + + public static FingerprintStatus createActiveUndecided() { + final FingerprintStatus status = new FingerprintStatus(); + status.trust = Trust.UNDECIDED; + status.active = true; + status.lastActivation = System.currentTimeMillis(); + return status; + } + + public static FingerprintStatus createActiveTrusted() { + final FingerprintStatus status = new FingerprintStatus(); + status.trust = Trust.TRUSTED; + status.active = true; + status.lastActivation = System.currentTimeMillis(); + return status; + } + + public static FingerprintStatus createActiveVerified(boolean x509) { + final FingerprintStatus status = new FingerprintStatus(); + status.trust = x509 ? Trust.VERIFIED_X509 : Trust.VERIFIED; + status.active = true; + return status; + } + + public static FingerprintStatus createActive(boolean trusted) { + final FingerprintStatus status = new FingerprintStatus(); + status.trust = trusted ? Trust.TRUSTED : Trust.UNTRUSTED; + status.active = true; + return status; + } + + public boolean isTrustedAndActive() { + return active && isTrusted(); + } + + public boolean isTrusted() { + return trust == Trust.TRUSTED || isVerified(); + } + + public boolean isVerified() { + return trust == Trust.VERIFIED || trust == Trust.VERIFIED_X509; + } + + public boolean isCompromised() { + return trust == Trust.COMPROMISED; + } + + public boolean isActive() { + return active; + } + + public FingerprintStatus toActive() { + FingerprintStatus status = new FingerprintStatus(); + status.trust = trust; + if (!status.active) { + status.lastActivation = System.currentTimeMillis(); + } + status.active = true; + return status; + } + + public FingerprintStatus toInactive() { + FingerprintStatus status = new FingerprintStatus(); + status.trust = trust; + status.active = false; + return status; + } + + public Trust getTrust() { + return trust; + } + + public static FingerprintStatus createCompromised() { + FingerprintStatus status = new FingerprintStatus(); + status.active = false; + status.trust = Trust.COMPROMISED; + return status; + } + + public FingerprintStatus toVerified() { + FingerprintStatus status = new FingerprintStatus(); + status.active = active; + status.trust = Trust.VERIFIED; + return status; + } + + public static FingerprintStatus createInactiveVerified() { + final FingerprintStatus status = new FingerprintStatus(); + status.trust = Trust.VERIFIED; + status.active = false; + return status; + } + + @Override + public int compareTo(FingerprintStatus o) { + if (active == o.active) { + if (lastActivation > o.lastActivation) { + return -1; + } else if (lastActivation < o.lastActivation) { + return 1; + } else { + return 0; + } + } else if (active){ + return -1; + } else { + return 1; + } + } + + public long getLastActivation() { + return lastActivation; + } + + public enum Trust { + COMPROMISED, + UNDECIDED, + UNTRUSTED, + TRUSTED, + VERIFIED, + VERIFIED_X509 + } + +} diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java index 4eb73313..13858b74 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java @@ -21,7 +21,10 @@ import java.util.Set; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; public class SQLiteAxolotlStore implements AxolotlStore { @@ -35,7 +38,10 @@ public class SQLiteAxolotlStore implements AxolotlStore { 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 TRUSTED = "trusted"; //no longer used + public static final String TRUST = "trust"; + public static final String ACTIVE = "active"; + public static final String LAST_ACTIVATION = "last_activation"; public static final String OWN = "ownkey"; public static final String CERTIFICATE = "certificate"; @@ -51,11 +57,11 @@ public class SQLiteAxolotlStore implements AxolotlStore { private int localRegistrationId; private int currentPreKeyId = 0; - private final LruCache<String, XmppAxolotlSession.Trust> trustCache = - new LruCache<String, XmppAxolotlSession.Trust>(NUM_TRUSTS_TO_CACHE) { + private final LruCache<String, FingerprintStatus> trustCache = + new LruCache<String, FingerprintStatus>(NUM_TRUSTS_TO_CACHE) { @Override - protected XmppAxolotlSession.Trust create(String fingerprint) { - return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint); + protected FingerprintStatus create(String fingerprint) { + return mXmppConnectionService.databaseBackend.getFingerprintStatus(account, fingerprint); } }; @@ -185,7 +191,20 @@ public class SQLiteAxolotlStore implements AxolotlStore { @Override public void saveIdentity(String name, IdentityKey identityKey) { if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) { - mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey); + String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); + FingerprintStatus status = getFingerprintStatus(fingerprint); + if (status == null) { + if (mXmppConnectionService.blindTrustBeforeVerification() && !account.getAxolotlService().hasVerifiedKeys(name)) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": blindly trusted "+fingerprint+" of "+name); + status = FingerprintStatus.createActiveTrusted(); + } else { + status = FingerprintStatus.createActiveUndecided(); + } + } else { + status = status.toActive(); + } + mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey, status); + trustCache.remove(fingerprint); } } @@ -208,12 +227,12 @@ public class SQLiteAxolotlStore implements AxolotlStore { return true; } - public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) { + public FingerprintStatus getFingerprintStatus(String fingerprint) { return (fingerprint == null)? null : trustCache.get(fingerprint); } - public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) { - mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust); + public void setFingerprintStatus(String fingerprint, FingerprintStatus status) { + mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, status); trustCache.remove(fingerprint); } @@ -225,8 +244,8 @@ public class SQLiteAxolotlStore implements AxolotlStore { return mXmppConnectionService.databaseBackend.getIdentityKeyCertifcate(account, fingerprint); } - public Set<IdentityKey> getContactKeysWithTrust(String bareJid, XmppAxolotlSession.Trust trust) { - return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust); + public Set<IdentityKey> getContactKeysWithTrust(String bareJid, FingerprintStatus status) { + return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, status); } public long getContactNumTrustedKeys(String bareJid) { @@ -428,4 +447,8 @@ public class SQLiteAxolotlStore implements AxolotlStore { public void removeSignedPreKey(int signedPreKeyId) { mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId); } + + public void preVerifyFingerprint(Account account, String name, String fingerprint) { + mXmppConnectionService.databaseBackend.storePreVerification(account,name,fingerprint,FingerprintStatus.createInactiveVerified()); + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java index 0b3164f8..981b93ec 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -204,13 +204,13 @@ public class XmppAxolotlMessage { 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)); + keyElement.setContent(Base64.encodeToString(keyEntry.getValue(), Base64.NO_WRAP)); headerElement.addChild(keyElement); } - headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT)); + headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.NO_WRAP)); if (ciphertext != null) { Element payload = encryptionElement.addChild(PAYLOAD); - payload.setContent(Base64.encodeToString(ciphertext, Base64.DEFAULT)); + payload.setContent(Base64.encodeToString(ciphertext, Base64.NO_WRAP)); } return encryptionElement; } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index b7d11ec0..725757a3 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -19,13 +19,10 @@ 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 eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; -public class XmppAxolotlSession { +public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> { private final SessionCipher cipher; private final SQLiteAxolotlStore sqLiteAxolotlStore; private final AxolotlAddress remoteAddress; @@ -34,76 +31,6 @@ public class XmppAxolotlSession { 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; @@ -145,79 +72,73 @@ public class XmppAxolotlSession { this.fresh = false; } - protected void setTrust(Trust trust) { - sqLiteAxolotlStore.setFingerprintTrust(getFingerprint(), trust); + protected void setTrust(FingerprintStatus status) { + sqLiteAxolotlStore.setFingerprintStatus(getFingerprint(), status); } - protected Trust getTrust() { - Trust trust = sqLiteAxolotlStore.getFingerprintTrust(getFingerprint()); - return (trust == null) ? Trust.UNDECIDED : trust; + public FingerprintStatus getTrust() { + FingerprintStatus status = sqLiteAxolotlStore.getFingerprintStatus(getFingerprint()); + return (status == null) ? FingerprintStatus.createActiveUndecided() : status; } @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: + FingerprintStatus status = getTrust(); + if (!status.isCompromised()) { + try { try { - try { - PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey); - if (!message.getPreKeyId().isPresent()) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage did not contain a PreKeyId"); - break; - } - Log.i(Config.LOGTAG, AxolotlService.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, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint()); - } else { - this.identityKey = msgIdentityKey; - plaintext = cipher.decrypt(message); - preKeyId = message.getPreKeyId().get(); - } - } catch (InvalidMessageException | InvalidVersionException e) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received"); - WhisperMessage message = new WhisperMessage(encryptedKey); + PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey); + if (!message.getPreKeyId().isPresent()) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage did not contain a PreKeyId"); + return null; + } + Log.i(Config.LOGTAG, AxolotlService.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, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint()); + } else { + this.identityKey = msgIdentityKey; plaintext = cipher.decrypt(message); - } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); + preKeyId = message.getPreKeyId().get(); } - } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) { + } catch (InvalidMessageException | InvalidVersionException e) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received"); + WhisperMessage message = new WhisperMessage(encryptedKey); + plaintext = cipher.decrypt(message); + } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); } + } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) { + Log.w(Config.LOGTAG, AxolotlService.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); - } + if (plaintext != null) { + if (!status.isActive()) { + setTrust(status.toActive()); } - - break; - - case COMPROMISED: - default: - // ignore - break; + } } return plaintext; } @Nullable public byte[] processSending(@NonNull byte[] outgoingMessage) { - Trust trust = getTrust(); - if (trust.trusted()) { + FingerprintStatus status = getTrust(); + if (status.isTrustedAndActive()) { CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); return ciphertextMessage.serialize(); } else { return null; } } + + public Account getAccount() { + return account; + } + + @Override + public int compareTo(XmppAxolotlSession o) { + return getTrust().compareTo(o.getTrust()); + } } diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 7d60dcf7..bb89cf17 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -15,16 +15,20 @@ import org.json.JSONObject; import java.security.PublicKey; import java.security.interfaces.DSAPublicKey; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.OtrService; import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -337,6 +341,10 @@ public class Account extends AbstractEntity { } } + public State getTrueStatus() { + return this.status; + } + public void setStatus(final State status) { this.status = status; } @@ -486,7 +494,7 @@ public class Account extends AbstractEntity { if (publicKey == null || !(publicKey instanceof DSAPublicKey)) { return null; } - this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey); + this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey).toLowerCase(Locale.US); return this.otrFingerprint; } catch (final OtrCryptoException ignored) { return null; @@ -599,12 +607,44 @@ public class Account extends AbstractEntity { } public String getShareableUri() { - final String fingerprint = this.getOtrFingerprint(); - if (fingerprint != null) { - return "xmpp:" + this.getJid().toBareJid().toString() + "?otr-fingerprint="+fingerprint; + List<XmppUri.Fingerprint> fingerprints = this.getFingerprints(); + String uri = "xmpp:"+this.getJid().toBareJid().toString(); + if (fingerprints.size() > 0) { + StringBuilder builder = new StringBuilder(uri); + builder.append('?'); + for(int i = 0; i < fingerprints.size(); ++i) { + XmppUri.FingerprintType type = fingerprints.get(i).type; + if (type == XmppUri.FingerprintType.OMEMO) { + builder.append(XmppUri.OMEMO_URI_PARAM); + builder.append(fingerprints.get(i).deviceId); + } else if (type == XmppUri.FingerprintType.OTR) { + builder.append(XmppUri.OTR_URI_PARAM); + } + builder.append('='); + builder.append(fingerprints.get(i).fingerprint); + if (i != fingerprints.size() -1) { + builder.append(';'); + } + } + return builder.toString(); } else { - return "xmpp:" + this.getJid().toBareJid().toString(); + return uri; + } + } + + private List<XmppUri.Fingerprint> getFingerprints() { + ArrayList<XmppUri.Fingerprint> fingerprints = new ArrayList<>(); + final String otr = this.getOtrFingerprint(); + if (otr != null) { + fingerprints.add(new XmppUri.Fingerprint(XmppUri.FingerprintType.OTR,otr)); + } + fingerprints.add(new XmppUri.Fingerprint(XmppUri.FingerprintType.OMEMO,axolotlService.getOwnFingerprint().substring(2),axolotlService.getOwnDeviceId())); + for(XmppAxolotlSession session : axolotlService.findOwnSessions()) { + if (session.getTrust().isVerified() && session.getTrust().isActive()) { + fingerprints.add(new XmppUri.Fingerprint(XmppUri.FingerprintType.OMEMO,session.getFingerprint().substring(2).replaceAll("\\s",""),session.getRemoteAddress().getDeviceId())); + } } + return fingerprints; } public boolean isBlocked(final ListItem contact) { diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index 70af45d4..b7307a8b 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -121,7 +121,7 @@ public class Contact implements ListItem, Blockable { } else if (this.presenceName != null && mutualPresenceSubscription()) { return this.presenceName; } else if (jid.hasLocalpart()) { - return jid.getLocalpart(); + return jid.getUnescapedLocalpart(); } else { return jid.getDomainpart(); } @@ -196,7 +196,7 @@ public class Contact implements ListItem, Blockable { values.put(ACCOUNT, accountUuid); values.put(SYSTEMNAME, systemName); values.put(SERVERNAME, serverName); - values.put(JID, jid.toString()); + values.put(JID, jid.toPreppedString()); values.put(OPTIONS, subscription); values.put(SYSTEMACCOUNT, systemAccount); values.put(PHOTOURI, photoUri); @@ -209,10 +209,6 @@ public class Contact implements ListItem, Blockable { } } - public int getSubscription() { - return this.subscription; - } - public Account getAccount() { return this.account; } @@ -305,7 +301,7 @@ public class Contact implements ListItem, Blockable { 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)); + fingerprints.add(prints.getString(i).toLowerCase(Locale.US)); } } } diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index d9a03fc9..ced48913 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.Locale; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.PgpDecryptionService; @@ -463,7 +464,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl if (generatedName != null) { return generatedName; } else { - return getJid().getLocalpart(); + return getJid().getUnescapedLocalpart(); } } } else { @@ -506,7 +507,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl values.put(NAME, name); values.put(CONTACT, contactUuid); values.put(ACCOUNT, accountUuid); - values.put(CONTACTJID, contactJid.toString()); + values.put(CONTACTJID, contactJid.toPreppedString()); values.put(CREATED, created); values.put(STATUS, status); values.put(MODE, mode); @@ -627,7 +628,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl return null; } DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession().getRemotePublicKey(); - this.otrFingerprint = getAccount().getOtrService().getFingerprint(remotePubKey); + this.otrFingerprint = getAccount().getOtrService().getFingerprint(remotePubKey).toLowerCase(Locale.US); } catch (final OtrCryptoException | UnsupportedOperationException ignored) { return null; } @@ -697,36 +698,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl } public int getNextEncryption() { - final AxolotlService axolotlService = getAccount().getAxolotlService(); - int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1); - if (next == -1) { - if (Config.supportOmemo() - && axolotlService != null - && mode == MODE_SINGLE - && axolotlService.isConversationAxolotlCapable(this) - && getAccount().getSelfContact().getPresences().allOrNonSupport(AxolotlService.PEP_DEVICE_LIST_NOTIFY) - && getContact().getPresences().allOrNonSupport(AxolotlService.PEP_DEVICE_LIST_NOTIFY)) { - return Message.ENCRYPTION_AXOLOTL; - } else { - next = this.getMostRecentlyUsedIncomingEncryption(); - } - } - - if (!Config.supportUnencrypted() && next <= 0) { - if (Config.supportOmemo() - && ((axolotlService != null && axolotlService.isConversationAxolotlCapable(this)) || !Config.multipleEncryptionChoices())) { - return Message.ENCRYPTION_AXOLOTL; - } else if (Config.supportOtr() && mode == MODE_SINGLE) { - return Message.ENCRYPTION_OTR; - } else if (Config.supportOpenPgp() - && (mode == MODE_SINGLE) || !Config.multipleEncryptionChoices()) { - return Message.ENCRYPTION_PGP; - } - } else if (next == Message.ENCRYPTION_AXOLOTL - && (!Config.supportOmemo() || axolotlService == null || !axolotlService.isConversationAxolotlCapable(this))) { - next = Message.ENCRYPTION_NONE; - } - return next; + return Math.max(this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, Message.ENCRYPTION_NONE), Message.ENCRYPTION_NONE); } public void setNextEncryption(int encryption) { diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index e3577b06..20e4c5a5 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -2,11 +2,13 @@ package eu.siacs.conversations.entities; import android.content.ContentValues; import android.database.Cursor; +import android.text.SpannableStringBuilder; import java.net.MalformedURLException; import java.net.URL; import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.GeoHelper; @@ -19,8 +21,6 @@ public class Message extends AbstractEntity { public static final String TABLENAME = "messages"; - public static final String MERGE_SEPARATOR = "\n\u200B\n"; - public static final int STATUS_RECEIVED = 0; public static final int STATUS_UNSEND = 1; public static final int STATUS_SEND = 2; @@ -59,6 +59,7 @@ public class Message extends AbstractEntity { 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 ERROR_MESSAGE = "errorMsg"; public static final String ME_COMMAND = "/me "; @@ -84,6 +85,7 @@ public class Message extends AbstractEntity { private Message mNextMessage = null; private Message mPreviousMessage = null; private String axolotlFingerprint = null; + private String errorMessage = null; private Message() { @@ -110,7 +112,8 @@ public class Message extends AbstractEntity { null, true, null, - false); + false, + null); this.conversation = conversation; } @@ -119,7 +122,7 @@ public class Message extends AbstractEntity { 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, final boolean oob) { + final String edited, final boolean oob, final String errorMessage) { this.uuid = uuid; this.conversationUuid = conversationUUid; this.counterpart = counterpart; @@ -137,6 +140,7 @@ public class Message extends AbstractEntity { this.read = read; this.edited = edited; this.oob = oob; + this.errorMessage = errorMessage; } public static Message fromCursor(Cursor cursor) { @@ -178,7 +182,8 @@ public class Message extends AbstractEntity { cursor.getString(cursor.getColumnIndex(FINGERPRINT)), cursor.getInt(cursor.getColumnIndex(READ)) > 0, cursor.getString(cursor.getColumnIndex(EDITED)), - cursor.getInt(cursor.getColumnIndex(OOB)) > 0); + cursor.getInt(cursor.getColumnIndex(OOB)) > 0, + cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE))); } public static Message createStatusMessage(Conversation conversation, String body) { @@ -205,12 +210,12 @@ public class Message extends AbstractEntity { if (counterpart == null) { values.putNull(COUNTERPART); } else { - values.put(COUNTERPART, counterpart.toString()); + values.put(COUNTERPART, counterpart.toPreppedString()); } if (trueCounterpart == null) { values.putNull(TRUE_COUNTERPART); } else { - values.put(TRUE_COUNTERPART, trueCounterpart.toString()); + values.put(TRUE_COUNTERPART, trueCounterpart.toPreppedString()); } values.put(BODY, body); values.put(TIME_SENT, timeSent); @@ -225,6 +230,7 @@ public class Message extends AbstractEntity { values.put(READ,read ? 1 : 0); values.put(EDITED, edited); values.put(OOB, oob ? 1 : 0); + values.put(ERROR_MESSAGE,errorMessage); return values; } @@ -272,6 +278,17 @@ public class Message extends AbstractEntity { this.body = body; } + public String getErrorMessage() { + return errorMessage; + } + + public boolean setErrorMessage(String message) { + boolean changed = (message != null && !message.equals(errorMessage)) + || (message == null && errorMessage != null); + this.errorMessage = message; + return changed; + } + public long getTimeSent() { return timeSent; } @@ -476,7 +493,7 @@ public class Message extends AbstractEntity { !this.getBody().startsWith(ME_COMMAND) && !this.bodyIsHeart() && !message.bodyIsHeart() && - this.isTrusted() == message.isTrusted() + ((this.axolotlFingerprint == null && message.axolotlFingerprint == null) || this.axolotlFingerprint.equals(message.getFingerprint())) ); } @@ -491,22 +508,26 @@ public class Message extends AbstractEntity { ); } - public String getMergedBody() { - StringBuilder body = new StringBuilder(this.body.trim()); + public static class MergeSeparator {} + + public SpannableStringBuilder getMergedBody() { + SpannableStringBuilder body = new SpannableStringBuilder(this.body.trim()); Message current = this; - while(current.mergeable(current.next())) { + while (current.mergeable(current.next())) { current = current.next(); if (current == null) { break; } - body.append(MERGE_SEPARATOR); + body.append("\n\n"); + body.setSpan(new MergeSeparator(), body.length() - 2, body.length(), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); body.append(current.getBody().trim()); } - return body.toString(); + return body; } public boolean hasMeCommand() { - return getMergedBody().startsWith(ME_COMMAND); + return this.body.trim().startsWith(ME_COMMAND); } public int getMergedStatus() { @@ -592,7 +613,7 @@ public class Message extends AbstractEntity { if (path == null || path.isEmpty()) { return null; } - + String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase(); int dotPosition = filename.lastIndexOf("."); @@ -791,8 +812,8 @@ public class Message extends AbstractEntity { } public boolean isTrusted() { - XmppAxolotlSession.Trust t = conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint); - return t != null && t.trusted(); + FingerprintStatus s = conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint); + return s != null && s.isTrusted(); } private int getPreviousEncryption() { diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index fa6afcfa..bbc3e370 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -3,7 +3,6 @@ package eu.siacs.conversations.entities; import android.annotation.SuppressLint; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -395,10 +394,20 @@ public class MucOptions { if (user != null) { synchronized (users) { users.remove(user); - if (membersOnly() && - nonanonymous() && - user.affiliation.ranks(Affiliation.MEMBER) && - user.realJid != null) { + boolean realJidInMuc = false; + for (User u : users) { + if (user.realJid != null && user.realJid.equals(u.realJid)) { + realJidInMuc = true; + break; + } + } + boolean self = user.realJid != null && user.realJid.equals(account.getJid().toBareJid()); + if (membersOnly() + && nonanonymous() + && user.affiliation.ranks(Affiliation.MEMBER) + && user.realJid != null + && !realJidInMuc + && !self) { user.role = Role.NONE; user.avatar = null; user.fullJid = null; @@ -409,7 +418,7 @@ public class MucOptions { return user; } - public void addUser(User user) { + public void updateUser(User user) { User old; if (user.fullJid == null && user.realJid != null) { old = findUserByRealJid(user.realJid); @@ -435,7 +444,10 @@ public class MucOptions { if (old != null) { users.remove(old); } - this.users.add(user); + if ((!membersOnly() || user.getAffiliation().ranks(Affiliation.MEMBER)) + && user.getAffiliation().outranks(Affiliation.OUTCAST)){ + this.users.add(user); + } } } @@ -505,8 +517,20 @@ public class MucOptions { } public List<User> getUsers(int max) { - ArrayList<User> users = getUsers(); - return users.subList(0, Math.min(max, users.size())); + ArrayList<User> subset = new ArrayList<>(); + HashSet<Jid> jids = new HashSet<>(); + jids.add(account.getJid().toBareJid()); + synchronized (users) { + for(User user : users) { + if (user.getRealJid() == null || jids.add(user.getRealJid())) { + subset.add(user); + } + if (subset.size() >= max) { + break; + } + } + } + return subset; } public int getUserCount() { diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java index f9fed914..f8639885 100644 --- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java @@ -38,13 +38,17 @@ public class PresenceGenerator extends AbstractGenerator { } public PresencePacket selfPresence(Account account, Presence.Status status) { + return selfPresence(account, status, true); + } + + public PresencePacket selfPresence(Account account, Presence.Status status, boolean includePgpAnnouncement) { 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 && mXmppConnectionService.getPgpEngine() != null) { + if (includePgpAnnouncement && sig != null && mXmppConnectionService.getPgpEngine() != null) { packet.addChild("x", "jabber:x:signed").setContent(sig); } String capHash = getCapHash(); diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java index a8b31a7a..18c60bff 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java @@ -24,6 +24,7 @@ import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.SSLSocketHelper; +import eu.siacs.conversations.utils.TLSSocketFactory; public class HttpConnectionManager extends AbstractConnectionManager { @@ -77,18 +78,7 @@ public class HttpConnectionManager extends AbstractConnectionManager { new StrictHostnameVerifier()); } try { - final SSLContext sc = SSLSocketHelper.getSSLContext(); - sc.init(null, new X509TrustManager[]{trustManager}, - mXmppConnectionService.getRNG()); - - final SSLSocketFactory sf = sc.getSocketFactory(); - final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( - sf.getSupportedCipherSuites()); - if (cipherSuites.length > 0) { - sc.getDefaultSSLParameters().setCipherSuites(cipherSuites); - - } - + final SSLSocketFactory sf = new TLSSocketFactory(new X509TrustManager[]{trustManager}, mXmppConnectionService.getRNG()); connection.setSSLSocketFactory(sf); connection.setHostnameVerifier(hostnameVerifier); } catch (final KeyManagementException | NoSuchAlgorithmException ignored) { @@ -96,6 +86,6 @@ public class HttpConnectionManager extends AbstractConnectionManager { } public Proxy getProxy() throws IOException { - return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getLocalHost(), 8118)); + return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getByAddress(new byte[]{127,0,0,1}), 8118)); } } diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index a9bffe3e..87f62706 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -25,6 +25,7 @@ import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.FileWriterException; public class HttpDownloadConnection implements Transferable { @@ -141,16 +142,12 @@ public class HttpDownloadConnection implements Transferable { mXmppConnectionService.updateConversationUi(); } - private class WriteException extends IOException { - - } - private void showToastForException(Exception e) { 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 WriteException) { + } else if (e instanceof FileWriterException) { mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_write_file); } else if (!(e instanceof CancellationException)) { mXmppConnectionService.showErrorToastInUi(R.string.download_failed_file_not_found); @@ -305,7 +302,7 @@ public class HttpDownloadConnection implements Transferable { try { os.write(buffer, 0, count); } catch (IOException e) { - throw new WriteException(); + throw new FileWriterException(); } updateProgress((int) ((((double) transmitted) / expected) * 100)); if (canceled) { @@ -315,7 +312,7 @@ public class HttpDownloadConnection implements Transferable { try { os.flush(); } catch (IOException e) { - throw new WriteException(); + throw new FileWriterException(); } } catch (CancellationException | IOException e) { throw e; diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index 1bd6a8e4..63a3884b 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -20,6 +20,7 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; @@ -86,10 +87,10 @@ public class HttpUploadConnection implements Transferable { this.canceled = true; } - private void fail() { + private void fail(String errorMessage) { mHttpConnectionManager.finishUploadConnection(this); message.setTransferable(null); - mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED, errorMessage); FileBackend.close(mFileInputStream); } @@ -111,7 +112,7 @@ public class HttpUploadConnection implements Transferable { pair = AbstractConnectionManager.createInputStream(file, true); } catch (FileNotFoundException e) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not find file to upload - "+e.getMessage()); - fail(); + fail(e.getMessage()); return; } this.file.setExpectedSize(pair.second); @@ -137,7 +138,7 @@ public class HttpUploadConnection implements Transferable { } } Log.d(Config.LOGTAG,account.getJid().toString()+": invalid response to slot request "+packet); - fail(); + fail(IqParser.extractErrorMessage(packet)); } }); message.setTransferable(this); @@ -206,12 +207,12 @@ public class HttpUploadConnection implements Transferable { @Override public void error(int errorCode, Message object) { Log.d(Config.LOGTAG,"pgp encryption failed"); - fail(); + fail("pgp encryption failed"); } @Override public void userInputRequried(PendingIntent pi, Message object) { - fail(); + fail("pgp encryption failed"); } }); } else { @@ -219,12 +220,12 @@ public class HttpUploadConnection implements Transferable { } } else { Log.d(Config.LOGTAG,"http upload failed because response code was "+code); - fail(); + fail("http upload failed because response code was "+code); } } catch (IOException e) { e.printStackTrace(); Log.d(Config.LOGTAG,"http upload failed "+e.getMessage()); - fail(); + fail(e.getMessage()); } finally { FileBackend.close(mFileInputStream); FileBackend.close(os); diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index b548b3c8..b4859e90 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -74,22 +74,41 @@ public abstract class AbstractParser { } public static MucOptions.User parseItem(Conversation conference, Element item) { + return parseItem(conference,item, null); + } + + public static MucOptions.User parseItem(Conversation conference, Element item, Jid fullJid) { final String local = conference.getJid().getLocalpart(); final String domain = conference.getJid().getDomainpart(); String affiliation = item.getAttribute("affiliation"); String role = item.getAttribute("role"); String nick = item.getAttribute("nick"); - Jid fullJid; - try { - fullJid = nick != null ? Jid.fromParts(local, domain, nick) : null; - } catch (InvalidJidException e) { - fullJid = null; + if (nick != null && fullJid == null) { + try { + fullJid = Jid.fromParts(local, domain, nick); + } catch (InvalidJidException e) { + fullJid = null; + } } Jid realJid = item.getAttributeAsJid("jid"); - MucOptions.User user = new MucOptions.User(conference.getMucOptions(), nick == null ? null : fullJid); + MucOptions.User user = new MucOptions.User(conference.getMucOptions(), fullJid); user.setRealJid(realJid); user.setAffiliation(affiliation); user.setRole(role); return user; } + + public static String extractErrorMessage(Element packet) { + final Element error = packet.findChild("error"); + if (error != null && error.getChildren().size() > 0) { + final String text = error.findChildContent("text"); + if (text != null && !text.trim().isEmpty()) { + return text; + } else { + return error.getChildren().get(0).getName().replace("-"," "); + } + } else { + return null; + } + } } diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java index 49b0db21..40eec0d2 100644 --- a/src/main/java/eu/siacs/conversations/parser/IqParser.java +++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java @@ -26,6 +26,7 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; @@ -319,6 +320,14 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } } account.getBlocklist().addAll(jids); + if (packet.getType() == IqPacket.TYPE.SET) { + for(Jid jid : jids) { + Conversation conversation = mXmppConnectionService.find(account,jid); + if (conversation != null) { + mXmppConnectionService.markRead(conversation); + } + } + } } // Update the UI mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 81b68ed9..c8ae1fdc 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -31,6 +31,7 @@ import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnMessagePacketReceived; import eu.siacs.conversations.xmpp.chatstate.ChatState; @@ -208,7 +209,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece 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()) + && Xmlns.STANZA_IDS.equals(child.getNamespace()) && by.equals(child.getAttributeAsJid("by"))) { return child.getAttribute("id"); } @@ -266,19 +267,15 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece 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(), - Message.STATUS_SEND_FAILED); - if (message != null && message.getEncryption() == Message.ENCRYPTION_OTR) { - message.getConversation().endOtrIfNeeded(); + Message.STATUS_SEND_FAILED, + extractErrorMessage(packet)); + if (message != null) { + if (message.getEncryption() == Message.ENCRYPTION_OTR) { + message.getConversation().endOtrIfNeeded(); + } } } return true; @@ -434,7 +431,18 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } if (serverMsgId == null) { - serverMsgId = extractStanzaId(packet, isTypeGroupChat ? conversation.getJid().toBareJid() : account.getServer()); + final Jid by; + final boolean safeToExtract; + if (isTypeGroupChat) { + by = conversation.getJid().toBareJid(); + safeToExtract = true; //conversation.getMucOptions().hasFeature(Xmlns.STANZA_IDS); + } else { + by = account.getJid().toBareJid(); + safeToExtract = true; //account.getXmppConnection().getFeatures().stanzaIds(); + } + if (safeToExtract) { + serverMsgId = extractStanzaId(packet, by); + } } message.setCounterpart(counterpart); @@ -604,7 +612,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece +user.getRealJid()+" to "+user.getAffiliation()+" in " +conversation.getJid().toBareJid()); if (!user.realJidMatchesAccount()) { - conversation.getMucOptions().addUser(user); + conversation.getMucOptions().updateUser(user); mXmppConnectionService.getAvatarService().clear(conversation); mXmppConnectionService.updateMucRosterUi(); mXmppConnectionService.updateConversationUi(); diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 46ce61b7..63bec8bf 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -38,7 +38,7 @@ public class PresenceParser extends AbstractParser implements boolean before = mucOptions.online(); int count = mucOptions.getUserCount(); final List<MucOptions.User> tileUserBefore = mucOptions.getUsers(5); - processConferencePresence(packet, mucOptions); + processConferencePresence(packet, conversation); final List<MucOptions.User> tileUserAfter = mucOptions.getUsers(5); if (!tileUserAfter.equals(tileUserBefore)) { mXmppConnectionService.getAvatarService().clear(mucOptions); @@ -51,7 +51,8 @@ public class PresenceParser extends AbstractParser implements } } - private void processConferencePresence(PresencePacket packet, MucOptions mucOptions) { + private void processConferencePresence(PresencePacket packet, Conversation conversation) { + MucOptions mucOptions = conversation.getMucOptions(); final Jid from = packet.getFrom(); if (!from.isBareJid()) { final String type = packet.getAttribute("type"); @@ -63,10 +64,7 @@ public class PresenceParser extends AbstractParser implements 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.setRealJid(item.getAttributeAsJid("jid")); + MucOptions.User user = parseItem(conversation, item, from); if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(mucOptions.getConversation().getJid())) { mucOptions.setOnline(); mucOptions.setSelf(user); @@ -77,7 +75,7 @@ public class PresenceParser extends AbstractParser implements mucOptions.mNickChangingInProgress = false; } } else { - mucOptions.addUser(user); + mucOptions.updateUser(user); } if (codes.contains(MucOptions.STATUS_CODE_ROOM_CREATED) && mucOptions.autoPushConfiguration()) { Log.d(Config.LOGTAG,mucOptions.getAccount().getJid().toBareJid() @@ -131,6 +129,10 @@ public class PresenceParser extends AbstractParser implements Log.d(Config.LOGTAG, "unknown error in conference: " + packet); } } else if (!from.isBareJid()){ + Element item = x.findChild("item"); + if (item != null) { + mucOptions.updateUser(parseItem(conversation, item, from)); + } MucOptions.User user = mucOptions.deleteUser(from); if (user != null) { mXmppConnectionService.getAvatarService().clear(user); diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index d8b6b4e1..63d5782b 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -27,17 +27,19 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import org.json.JSONException; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; -import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -45,7 +47,6 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.PresenceTemplate; import eu.siacs.conversations.entities.Roster; import eu.siacs.conversations.entities.ServiceDiscoveryResult; -import eu.siacs.conversations.generator.AbstractGenerator; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -54,7 +55,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 28; + private static final int DATABASE_VERSION = 33; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -129,7 +130,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { + SQLiteAxolotlStore.OWN + " INTEGER, " + SQLiteAxolotlStore.FINGERPRINT + " TEXT, " + SQLiteAxolotlStore.CERTIFICATE + " BLOB, " - + SQLiteAxolotlStore.TRUSTED + " INTEGER, " + + SQLiteAxolotlStore.TRUST + " TEXT, " + + SQLiteAxolotlStore.ACTIVE + " NUMBER, " + + SQLiteAxolotlStore.LAST_ACTIVATION + " NUMBER," + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + SQLiteAxolotlStore.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " @@ -139,6 +142,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { + ") ON CONFLICT IGNORE" + ");"; + private static String START_TIMES_TABLE = "start_times"; + + private static String CREATE_START_TIMES_TABLE = "create table "+START_TIMES_TABLE+" (timestamp NUMBER);"; + private DatabaseBackend(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @@ -181,6 +188,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Message.EDITED + " TEXT, " + Message.READ + " NUMBER DEFAULT 1, " + Message.OOB + " INTEGER, " + + Message.ERROR_MESSAGE + " TEXT," + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY(" + Message.CONVERSATION + ") REFERENCES " + Conversation.TABLENAME + "(" + Conversation.UUID @@ -193,6 +201,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); db.execSQL(CREATE_IDENTITIES_STATEMENT); db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT); + db.execSQL(CREATE_START_TIMES_TABLE); } @Override @@ -287,11 +296,20 @@ public class DatabaseBackend extends SQLiteOpenHelper { continue; } int ownDeviceId = Integer.valueOf(ownDeviceIdString); - AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), ownDeviceId); + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownDeviceId); deleteSession(db, account, ownAddress); IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account); if (identityKeyPair != null) { - setIdentityKeyTrust(db, account, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), XmppAxolotlSession.Trust.TRUSTED); + String[] selectionArgs = { + account.getUuid(), + identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", "") + }; + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.TRUSTED, 2); + db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.FINGERPRINT + " = ? ", + selectionArgs); } else { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not load own identity key pair"); } @@ -333,6 +351,50 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 28 && newVersion >= 28) { canonicalizeJids(db); } + + if (oldVersion < 29 && newVersion >= 29) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.ERROR_MESSAGE + " TEXT"); + } + if (oldVersion < 30 && newVersion >= 30) { + db.execSQL(CREATE_START_TIMES_TABLE); + } + if (oldVersion < 31 && newVersion >= 31) { + db.execSQL("ALTER TABLE "+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN "+SQLiteAxolotlStore.TRUST + " TEXT"); + db.execSQL("ALTER TABLE "+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN "+SQLiteAxolotlStore.ACTIVE + " NUMBER"); + HashMap<Integer,ContentValues> migration = new HashMap<>(); + migration.put(0,createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED,true)); + migration.put(1,createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true)); + migration.put(2,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, true)); + migration.put(3,createFingerprintStatusContentValues(FingerprintStatus.Trust.COMPROMISED, false)); + migration.put(4,createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false)); + migration.put(5,createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false)); + migration.put(6,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, false)); + migration.put(7,createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, true)); + migration.put(8,createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, false)); + for(Map.Entry<Integer,ContentValues> entry : migration.entrySet()) { + String whereClause = SQLiteAxolotlStore.TRUSTED+"=?"; + String[] where = {String.valueOf(entry.getKey())}; + db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME,entry.getValue(),whereClause,where); + } + + } + if (oldVersion < 32 && newVersion >= 32) { + db.execSQL("ALTER TABLE "+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN "+SQLiteAxolotlStore.LAST_ACTIVATION + " NUMBER"); + ContentValues defaults = new ContentValues(); + defaults.put(SQLiteAxolotlStore.LAST_ACTIVATION,System.currentTimeMillis()); + db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME,defaults,null,null); + } + if (oldVersion < 33 && newVersion >= 33) { + String whereClause = SQLiteAxolotlStore.OWN+"=1"; + db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME,createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED,true),whereClause,null); + } + } + + private static ContentValues createFingerprintStatusContentValues(FingerprintStatus.Trust trust, boolean active) { + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.TRUST,trust.toString()); + values.put(SQLiteAxolotlStore.ACTIVE,active ? 1 : 0); + return values; } private void canonicalizeJids(SQLiteDatabase db) { @@ -345,7 +407,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { try { newJid = Jid.fromString( cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) - ).toString(); + ).toPreppedString(); } catch (InvalidJidException ignored) { Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID " + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) @@ -370,7 +432,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { try { newJid = Jid.fromString( cursor.getString(cursor.getColumnIndex(Contact.JID)) - ).toString(); + ).toPreppedString(); } catch (InvalidJidException ignored) { Log.e(Config.LOGTAG, "Failed to migrate Contact JID " + cursor.getString(cursor.getColumnIndex(Contact.JID)) @@ -578,8 +640,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { public Conversation findConversation(final Account account, final Jid contactJid) { SQLiteDatabase db = this.getReadableDatabase(); String[] selectionArgs = {account.getUuid(), - contactJid.toBareJid().toString() + "/%", - contactJid.toBareJid().toString() + contactJid.toBareJid().toPreppedString() + "/%", + contactJid.toBareJid().toPreppedString() }; Cursor cursor = db.query(Conversation.TABLENAME, null, Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID @@ -691,7 +753,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { 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().toPreppedString()}; db.delete(Contact.TABLENAME, where, whereArgs); } } @@ -729,6 +791,20 @@ public class DatabaseBackend extends SQLiteOpenHelper { } } + public long getLastTimeFingerprintUsed(Account account, String fingerprint) { + String SQL = "select messages.timeSent from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and messages.axolotl_fingerprint=? order by messages.timesent desc limit 1"; + String[] args = {account.getUuid(), fingerprint}; + Cursor cursor = getReadableDatabase().rawQuery(SQL,args); + long time; + if (cursor.moveToFirst()) { + time = cursor.getLong(0); + } else { + time = 0; + } + cursor.close(); + return time; + } + public Pair<Long,String> getLastClearDate(Account account) { SQLiteDatabase db = this.getReadableDatabase(); String[] columns = {Conversation.ATTRIBUTES}; @@ -993,7 +1069,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { } private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) { - String[] columns = {SQLiteAxolotlStore.TRUSTED, + String[] columns = {SQLiteAxolotlStore.TRUST, + SQLiteAxolotlStore.ACTIVE, + SQLiteAxolotlStore.LAST_ACTIVATION, SQLiteAxolotlStore.KEY}; ArrayList<String> selectionArgs = new ArrayList<>(4); selectionArgs.add(account.getUuid()); @@ -1025,7 +1103,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { } private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) { - String name = account.getJid().toBareJid().toString(); + String name = account.getJid().toBareJid().toPreppedString(); IdentityKeyPair identityKeyPair = null; Cursor cursor = getIdentityKeyCursor(db, account, name, true); if (cursor.getCount() != 0) { @@ -1045,18 +1123,21 @@ public class DatabaseBackend extends SQLiteOpenHelper { return loadIdentityKeys(account, name, null); } - public Set<IdentityKey> loadIdentityKeys(Account account, String name, XmppAxolotlSession.Trust trust) { + public Set<IdentityKey> loadIdentityKeys(Account account, String name, FingerprintStatus status) { 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()) { + if (status != null && !FingerprintStatus.fromCursor(cursor).equals(status)) { continue; } try { - identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT), 0)); + String key = cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)); + if (key != null) { + identityKeys.add(new IdentityKey(Base64.decode(key, Base64.DEFAULT), 0)); + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Missing key (possibly preverified) in database for account" + account.getJid().toBareJid() + ", address: " + name); + } } catch (InvalidKeyException e) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); } @@ -1071,22 +1152,20 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] args = { account.getUuid(), name, - String.valueOf(XmppAxolotlSession.Trust.TRUSTED.getCode()), - String.valueOf(XmppAxolotlSession.Trust.TRUSTED_X509.getCode()) + FingerprintStatus.Trust.TRUSTED.toString(), + FingerprintStatus.Trust.VERIFIED.toString(), + FingerprintStatus.Trust.VERIFIED_X509.toString() }; return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME, SQLiteAxolotlStore.ACCOUNT + " = ?" + " AND " + SQLiteAxolotlStore.NAME + " = ?" - + " AND (" + SQLiteAxolotlStore.TRUSTED + " = ? OR " + SQLiteAxolotlStore.TRUSTED + " = ?)", + + " AND (" + SQLiteAxolotlStore.TRUST + " = ? OR " + SQLiteAxolotlStore.TRUST + " = ? OR " +SQLiteAxolotlStore.TRUST +" = ?)" + + " AND " +SQLiteAxolotlStore.ACTIVE + " > 0", 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) { + private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, FingerprintStatus status) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); @@ -1094,35 +1173,50 @@ public class DatabaseBackend extends SQLiteOpenHelper { values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0); values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint); values.put(SQLiteAxolotlStore.KEY, base64Serialized); - values.put(SQLiteAxolotlStore.TRUSTED, trusted.getCode()); + values.putAll(status.toContentValues()); + String where = SQLiteAxolotlStore.ACCOUNT+"=? AND "+SQLiteAxolotlStore.NAME+"=? AND "+SQLiteAxolotlStore.FINGERPRINT+" =?"; + String[] whereArgs = {account.getUuid(),name,fingerprint}; + int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME,values,where,whereArgs); + if (rows == 0) { + db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); + } + } + + public void storePreVerification(Account account, String name, String fingerprint, FingerprintStatus status) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + values.put(SQLiteAxolotlStore.NAME, name); + values.put(SQLiteAxolotlStore.OWN, 0); + values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint); + values.putAll(status.toContentValues()); db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); } - public XmppAxolotlSession.Trust isIdentityKeyTrusted(Account account, String fingerprint) { + public FingerprintStatus getFingerprintStatus(Account account, String fingerprint) { Cursor cursor = getIdentityKeyCursor(account, fingerprint); - XmppAxolotlSession.Trust trust = null; + final FingerprintStatus status; if (cursor.getCount() > 0) { cursor.moveToFirst(); - int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED)); - trust = XmppAxolotlSession.Trust.fromCode(trustValue); + status = FingerprintStatus.fromCursor(cursor); + } else { + status = null; } cursor.close(); - return trust; + return status; } - public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) { + public boolean setIdentityKeyTrust(Account account, String fingerprint, FingerprintStatus fingerprintStatus) { SQLiteDatabase db = this.getWritableDatabase(); - return setIdentityKeyTrust(db, account, fingerprint, trust); + return setIdentityKeyTrust(db, account, fingerprint, fingerprintStatus); } - private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, XmppAxolotlSession.Trust trust) { + private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, FingerprintStatus status) { String[] selectionArgs = { account.getUuid(), fingerprint }; - ContentValues values = new ContentValues(); - values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode()); - int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, + int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, status.toContentValues(), SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? ", selectionArgs); @@ -1176,12 +1270,12 @@ public class DatabaseBackend extends SQLiteOpenHelper { } } - 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 storeIdentityKey(Account account, String name, IdentityKey identityKey, FingerprintStatus status) { + storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT), status); } 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); + storeIdentityKey(account, account.getJid().toBareJid().toPreppedString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), FingerprintStatus.createActiveVerified(false)); } @@ -1217,4 +1311,35 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteAxolotlStore.ACCOUNT + " = ?", deleteArgs); } + + public boolean startTimeCountExceedsThreshold() { + SQLiteDatabase db = this.getWritableDatabase(); + long cleanBeforeTimestamp = System.currentTimeMillis() - Config.FREQUENT_RESTARTS_DETECTION_WINDOW; + db.execSQL("delete from "+START_TIMES_TABLE+" where timestamp < "+cleanBeforeTimestamp); + ContentValues values = new ContentValues(); + values.put("timestamp",System.currentTimeMillis()); + db.insert(START_TIMES_TABLE,null,values); + String[] columns = new String[]{"count(timestamp)"}; + Cursor cursor = db.query(START_TIMES_TABLE,columns,null,null,null,null,null); + int count; + if (cursor.moveToFirst()) { + count = cursor.getInt(0); + } else { + count = 0; + } + cursor.close(); + Log.d(Config.LOGTAG,"start time counter reached "+count); + return count >= Config.FREQUENT_RESTARTS_THRESHOLD; + } + + public void clearStartTimeCounter(boolean justOne) { + SQLiteDatabase db = this.getWritableDatabase(); + if (justOne) { + db.execSQL("delete from "+START_TIMES_TABLE+" where timestamp in (select timestamp from "+START_TIMES_TABLE+" order by timestamp desc limit 1)"); + Log.d(Config.LOGTAG,"do not count start up after being swiped away"); + } else { + Log.d(Config.LOGTAG,"resetting start time counter"); + db.execSQL("delete from " + START_TIMES_TABLE); + } + } } diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 921ac12a..84330d16 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -54,12 +54,13 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.ExifHelper; import eu.siacs.conversations.utils.FileUtils; +import eu.siacs.conversations.utils.FileWriterException; import eu.siacs.conversations.xmpp.pep.Avatar; public class FileBackend { private static final SimpleDateFormat IMAGE_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); - public static final String CONVERSATIONS_FILE_PROVIDER = "eu.siacs.conversations.files"; + private static final String FILE_PROVIDER = ".files"; private XmppConnectionService mXmppConnectionService; @@ -250,11 +251,21 @@ public class FileBackend { byte[] buffer = new byte[1024]; int length; while ((length = is.read(buffer)) > 0) { - os.write(buffer, 0, length); + try { + os.write(buffer, 0, length); + } catch (IOException e) { + throw new FileWriterException(); + } + } + try { + os.flush(); + } catch (IOException e) { + throw new FileWriterException(); } - os.flush(); } catch(FileNotFoundException e) { throw new FileCopyException(R.string.error_file_not_found); + } catch(FileWriterException e) { + throw new FileCopyException(R.string.error_unable_to_create_temporary_file); } catch (IOException e) { e.printStackTrace(); throw new FileCopyException(R.string.error_io_exception); @@ -299,8 +310,13 @@ public class FileBackend { InputStream is = null; OutputStream os = null; try { - file.createNewFile(); + if (!file.exists() && !file.createNewFile()) { + throw new FileCopyException(R.string.error_unable_to_create_temporary_file); + } is = mXmppConnectionService.getContentResolver().openInputStream(image); + if (is == null) { + throw new FileCopyException(R.string.error_not_an_image_file); + } Bitmap originalBitmap; BitmapFactory.Options options = new BitmapFactory.Options(); int inSampleSize = (int) Math.pow(2, sampleSize); @@ -327,7 +343,6 @@ public class FileBackend { quality -= 5; } scaledBitmap.recycle(); - return; } catch (FileNotFoundException e) { throw new FileCopyException(R.string.error_file_not_found); } catch (IOException e) { @@ -342,8 +357,6 @@ public class FileBackend { } else { throw new FileCopyException(R.string.error_out_of_memory); } - } catch (NullPointerException e) { - throw new FileCopyException(R.string.error_io_exception); } finally { close(os); close(is); @@ -458,12 +471,17 @@ public class FileBackend { } file.getParentFile().mkdirs(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N || Config.ONLY_INTERNAL_STORAGE) { - return FileProvider.getUriForFile(mXmppConnectionService, CONVERSATIONS_FILE_PROVIDER, file); + return getUriForFile(mXmppConnectionService,file); } else { return Uri.fromFile(file); } } + public static Uri getUriForFile(Context context, File file) { + String packageId = context.getPackageName(); + return FileProvider.getUriForFile(context, packageId + FILE_PROVIDER, file); + } + public static Uri getIndexableTakePhotoUri(Uri original) { if (Config.ONLY_INTERNAL_STORAGE || "file".equals(original.getScheme())) { return original; @@ -711,9 +729,7 @@ public class FileBackend { } public Uri getJingleFileUri(Message message) { - File file = getFile(message); - return FileProvider.getUriForFile(mXmppConnectionService, - CONVERSATIONS_FILE_PROVIDER, file); + return getUriForFile(mXmppConnectionService,getFile(message)); } public void updateFileParams(Message message) { diff --git a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java index 8d02f975..dfe4cb28 100644 --- a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java @@ -55,7 +55,7 @@ public class AbstractConnectionManager { } public boolean hasStoragePermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (!Config.ONLY_INTERNAL_STORAGE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return mXmppConnectionService.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; } else { return true; diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index 45a6fd81..4b4d1ed3 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -44,6 +44,9 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { } private Bitmap get(final Contact contact, final int size, boolean cachedOnly) { + if (contact.isSelf()) { + return get(contact.getAccount(),size,cachedOnly); + } final String KEY = key(contact, size); Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); if (avatar != null || cachedOnly) { @@ -169,7 +172,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded { if (bitmap != null || cachedOnly) { return bitmap; } - final List<MucOptions.User> users = mucOptions.getUsers(); + final List<MucOptions.User> users = mucOptions.getUsers(5); int count = users.size(); bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); diff --git a/src/main/java/eu/siacs/conversations/services/BarcodeProvider.java b/src/main/java/eu/siacs/conversations/services/BarcodeProvider.java new file mode 100644 index 00000000..9c50b081 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/services/BarcodeProvider.java @@ -0,0 +1,206 @@ +package eu.siacs.conversations.services; + +import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.net.Uri; +import android.os.CancellationSignal; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.aztec.AztecWriter; +import com.google.zxing.common.BitMatrix; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Hashtable; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.xmpp.jid.Jid; + +public class BarcodeProvider extends ContentProvider implements ServiceConnection { + + private static final String AUTHORITY = ".barcodes"; + + private final Object lock = new Object(); + + private XmppConnectionService mXmppConnectionService; + private boolean mBindingInProcess = false; + + @Override + public boolean onCreate() { + File barcodeDirectory = new File(getContext().getCacheDir().getAbsolutePath() + "/barcodes/"); + if (barcodeDirectory.exists() && barcodeDirectory.isDirectory()) { + for (File file : barcodeDirectory.listFiles()) { + if (file.isFile() && !file.isHidden()) { + Log.d(Config.LOGTAG, "deleting old barcode file " + file.getAbsolutePath()); + file.delete(); + } + } + } + return true; + } + + @Nullable + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return null; + } + + @Nullable + @Override + public String getType(Uri uri) { + return "image/png"; + } + + @Nullable + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { + return openFile(uri, mode, null); + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) throws FileNotFoundException { + Log.d(Config.LOGTAG, "opening file with uri (normal): " + uri.toString()); + String path = uri.getPath(); + if (path != null && path.endsWith(".png") && path.length() >= 5) { + String jid = path.substring(1).substring(0, path.length() - 4); + Log.d(Config.LOGTAG, "account:" + jid); + if (connectAndWait()) { + Log.d(Config.LOGTAG, "connected to background service"); + try { + Account account = mXmppConnectionService.findAccountByJid(Jid.fromString(jid)); + if (account != null) { + String shareableUri = account.getShareableUri(); + String hash = CryptoHelper.getFingerprint(shareableUri); + File file = new File(getContext().getCacheDir().getAbsolutePath() + "/barcodes/" + hash); + if (!file.exists()) { + file.getParentFile().mkdirs(); + file.createNewFile(); + Bitmap bitmap = createAztecBitmap(account.getShareableUri(), 1024); + OutputStream outputStream = new FileOutputStream(file); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream); + outputStream.close(); + outputStream.flush(); + } + return ParcelFileDescriptor.open(file,ParcelFileDescriptor.MODE_READ_ONLY); + } + } catch (Exception e) { + throw new FileNotFoundException(); + } + } + } + throw new FileNotFoundException(); + } + + private boolean connectAndWait() { + Intent intent = new Intent(getContext(), XmppConnectionService.class); + intent.setAction(this.getClass().getSimpleName()); + Context context = getContext(); + if (context != null) { + synchronized (this) { + if (mXmppConnectionService == null && !mBindingInProcess) { + Log.d(Config.LOGTAG,"calling to bind service"); + context.startService(intent); + context.bindService(intent, this, Context.BIND_AUTO_CREATE); + this.mBindingInProcess = true; + } + } + try { + waitForService(); + return true; + } catch (InterruptedException e) { + return false; + } + } else { + Log.d(Config.LOGTAG, "context was null"); + return false; + } + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (this) { + XmppConnectionService.XmppConnectionBinder binder = (XmppConnectionService.XmppConnectionBinder) service; + mXmppConnectionService = binder.getService(); + mBindingInProcess = false; + synchronized (this.lock) { + lock.notifyAll(); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (this) { + mXmppConnectionService = null; + } + } + + private void waitForService() throws InterruptedException { + if (mXmppConnectionService == null) { + synchronized (this.lock) { + lock.wait(); + } + } else { + Log.d(Config.LOGTAG,"not waiting for service because already initialized"); + } + } + + public static Uri getUriForAccount(Context context, Account account) { + final String packageId = context.getPackageName(); + return Uri.parse("content://" + packageId + AUTHORITY + "/" + account.getJid().toBareJid() + ".png"); + } + + public static Bitmap createAztecBitmap(String input, int size) { + try { + final AztecWriter AZTEC_WRITER = new AztecWriter(); + final Hashtable<EncodeHintType, Object> hints = new Hashtable<>(); + hints.put(EncodeHintType.ERROR_CORRECTION, 10); + final BitMatrix result = AZTEC_WRITER.encode(input, BarcodeFormat.AZTEC, size, size, hints); + final int width = result.getWidth(); + final int height = result.getHeight(); + final int[] pixels = new int[width * height]; + for (int y = 0; y < height; y++) { + final int offset = y * width; + for (int x = 0; x < width; x++) { + pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE; + } + } + final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return bitmap; + } catch (final Exception e) { + return null; + } + } +} diff --git a/src/main/java/eu/siacs/conversations/services/EventReceiver.java b/src/main/java/eu/siacs/conversations/services/EventReceiver.java index ab48435d..9b73035c 100644 --- a/src/main/java/eu/siacs/conversations/services/EventReceiver.java +++ b/src/main/java/eu/siacs/conversations/services/EventReceiver.java @@ -19,9 +19,6 @@ public class EventReceiver extends BroadcastReceiver { mIntentForService.setAction("other"); } final String action = intent.getAction(); - if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) && Config.PUSH_MODE) { - return; - } if (action.equals("ui") || DatabaseBackend.getInstance(context).hasEnabledAccounts()) { context.startService(mIntentForService); } diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 6e1d6c4b..47364b30 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -34,6 +34,7 @@ import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.ManageAccountActivity; +import eu.siacs.conversations.ui.SettingsActivity; import eu.siacs.conversations.ui.TimePreference; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.UIHelper; @@ -591,7 +592,7 @@ public class NotificationService { errors.add(account); } } - if (mXmppConnectionService.getPreferences().getBoolean("keep_foreground_service", false)) { + if (mXmppConnectionService.getPreferences().getBoolean(SettingsActivity.KEEP_FOREGROUND_SERVICE, false)) { notificationManager.notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification()); } final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 5650b894..b9aeffee 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -65,6 +65,7 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpDecryptionService; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; @@ -92,6 +93,7 @@ import eu.siacs.conversations.parser.MessageParser; import eu.siacs.conversations.parser.PresenceParser; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.ui.SettingsActivity; import eu.siacs.conversations.ui.UiCallback; import eu.siacs.conversations.utils.ConversationsFileObserver; import eu.siacs.conversations.utils.CryptoHelper; @@ -102,6 +104,7 @@ import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor; import eu.siacs.conversations.utils.SerialSingleThreadExecutor; import eu.siacs.conversations.utils.Xmlns; +import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnBindListener; import eu.siacs.conversations.xmpp.OnContactStatusChanged; @@ -145,6 +148,7 @@ public class XmppConnectionService extends Service { private final List<Conversation> conversations = new CopyOnWriteArrayList<>(); private final IqGenerator mIqGenerator = new IqGenerator(this); private final List<String> mInProgressAvatarFetches = new ArrayList<>(); + private final HashSet<Jid> mLowPingTimeoutMode = new HashSet<>(); private long mLastActivity = 0; @@ -294,6 +298,11 @@ public class XmppConnectionService extends Service { mOnAccountUpdate.onAccountUpdate(); } if (account.getStatus() == Account.State.ONLINE) { + synchronized (mLowPingTimeoutMode) { + if (mLowPingTimeoutMode.remove(account.getJid().toBareJid())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": leaving low ping timeout mode"); + } + } if (account.setShowErrorNotification(true)) { databaseBackend.updateAccount(account); } @@ -325,32 +334,35 @@ public class XmppConnectionService extends Service { joinMuc(conversation); } account.pendingConferenceJoins.clear(); - scheduleWakeUpCall(Config.PUSH_MODE ? Config.PING_MIN_INTERVAL : Config.PING_MAX_INTERVAL, account.getUuid().hashCode()); - } else if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED) { - resetSendingToWaiting(account); - final boolean disabled = account.isOptionSet(Account.OPTION_DISABLED); - final boolean listeners = checkListeners(); - final boolean pushMode = Config.PUSH_MODE - && mPushManagementService.available(account) - && listeners; - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": push mode="+Boolean.toString(pushMode)+" listeners="+Boolean.toString(listeners)); - 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, false); - } else if ((account.getStatus() != Account.State.CONNECTING) - && (account.getStatus() != Account.State.NO_INTERNET)) { - resetSendingToWaiting(account); - if (connection != null) { - int next = connection.getTimeToNextAttempt(); - Log.d(Config.LOGTAG, account.getJid().toBareJid() - + ": error connecting account. try again in " - + next + "s for the " - + (connection.getAttempt() + 1) + " time"); - scheduleWakeUpCall(next, account.getUuid().hashCode()); + scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode()); + } else { + if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED) { + resetSendingToWaiting(account); + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + synchronized (mLowPingTimeoutMode) { + if (mLowPingTimeoutMode.contains(account.getJid().toBareJid())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": went into offline state during low ping mode. reconnecting now"); + reconnectAccount(account, true, false); + } else { + int timeToReconnect = mRandom.nextInt(10) + 2; + scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode()); + } + } + } + } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) { + databaseBackend.updateAccount(account); + reconnectAccount(account, true, false); + } else if ((account.getStatus() != Account.State.CONNECTING) + && (account.getStatus() != Account.State.NO_INTERNET)) { + resetSendingToWaiting(account); + if (connection != null) { + int next = connection.getTimeToNextAttempt(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + + ": error connecting account. try again in " + + next + "s for the " + + (connection.getAttempt() + 1) + " time"); + scheduleWakeUpCall(next, account.getUuid().hashCode()); + } } } getNotificationService().updateErrorNotification(); @@ -537,7 +549,7 @@ public class XmppConnectionService extends Service { switch (action) { case ConnectivityManager.CONNECTIVITY_ACTION: if (hasInternetConnection() && Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) { - resetAllAttemptCounts(true); + resetAllAttemptCounts(true, false); } break; case ACTION_MERGE_PHONE_CONTACTS: @@ -556,14 +568,14 @@ public class XmppConnectionService extends Service { } break; case ACTION_DISABLE_FOREGROUND: - getPreferences().edit().putBoolean("keep_foreground_service", false).commit(); + getPreferences().edit().putBoolean(SettingsActivity.KEEP_FOREGROUND_SERVICE, false).commit(); toggleForegroundService(); break; case ACTION_DISMISS_ERROR_NOTIFICATIONS: dismissErrorNotifications(); break; case ACTION_TRY_AGAIN: - resetAllAttemptCounts(false); + resetAllAttemptCounts(false, true); interactive = true; break; case ACTION_REPLY_TO_CONVERSATION: @@ -591,8 +603,7 @@ public class XmppConnectionService extends Service { refreshAllGcmTokens(); break; case ACTION_IDLE_PING: - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && !Config.PUSH_MODE) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { scheduleNextIdlePing(); } break; @@ -602,31 +613,58 @@ public class XmppConnectionService extends Service { break; } } - this.wakeLock.acquire(); + synchronized (this) { + this.wakeLock.acquire(); + boolean pingNow = ConnectivityManager.CONNECTIVITY_ACTION.equals(action); + HashSet<Account> pingCandidates = new HashSet<>(); + for (Account account : accounts) { + pingNow |= processAccountState(account, + interactive, + "ui".equals(action), + CryptoHelper.getAccountFingerprint(account).equals(pushedAccountHash), + pingCandidates); + } + if (pingNow) { + for (Account account : pingCandidates) { + final boolean lowTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid()); + account.getXmppConnection().sendPing(); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping (action=" + action + ",lowTimeout=" + Boolean.toString(lowTimeout) + ")"); + scheduleWakeUpCall(lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT, account.getUuid().hashCode()); + } + } + if (wakeLock.isHeld()) { + try { + wakeLock.release(); + } catch (final RuntimeException ignored) { + } + } + } + return START_STICKY; + } + private boolean processAccountState(Account account, boolean interactive, boolean isUiAction, boolean isAccountPushed, HashSet<Account> pingCandidates) { boolean pingNow = false; - HashSet<Account> pingCandidates = new HashSet<>(); - - for (Account account : accounts) { - if (!account.isOptionSet(Account.OPTION_DISABLED)) { - if (!hasInternetConnection()) { - account.setStatus(Account.State.NO_INTERNET); + if (!account.isOptionSet(Account.OPTION_DISABLED)) { + if (!hasInternetConnection()) { + account.setStatus(Account.State.NO_INTERNET); + if (statusListener != null) { + statusListener.onStatusChanged(account); + } + } else { + if (account.getStatus() == Account.State.NO_INTERNET) { + account.setStatus(Account.State.OFFLINE); if (statusListener != null) { statusListener.onStatusChanged(account); } - } else { - if (account.getStatus() == Account.State.NO_INTERNET) { - account.setStatus(Account.State.OFFLINE); - if (statusListener != null) { - statusListener.onStatusChanged(account); - } - } - if (account.getStatus() == Account.State.ONLINE) { + } + if (account.getStatus() == Account.State.ONLINE) { + synchronized (mLowPingTimeoutMode) { long lastReceived = account.getXmppConnection().getLastPacketReceived(); long lastSent = account.getXmppConnection().getLastPingSent(); - long pingInterval = (Config.PUSH_MODE || "ui".equals(action)) ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000; + long pingInterval = isUiAction ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000; long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime(); - long pingTimeoutIn = (lastSent + Config.PING_TIMEOUT * 1000) - SystemClock.elapsedRealtime(); + int pingTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid()) ? Config.LOW_PING_TIMEOUT * 1000 : Config.PING_TIMEOUT * 1000; + long pingTimeoutIn = (lastSent + pingTimeout) - SystemClock.elapsedRealtime(); if (lastSent > lastReceived) { if (pingTimeoutIn < 0) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout"); @@ -637,62 +675,46 @@ public class XmppConnectionService extends Service { } } else { pingCandidates.add(account); - if (msToNextPing <= 0 || CryptoHelper.getAccountFingerprint(account).equals(pushedAccountHash)) { + if (isAccountPushed) { + pingNow = true; + if (mLowPingTimeoutMode.add(account.getJid().toBareJid())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": entering low ping timeout mode"); + } + } else if (msToNextPing <= 0) { pingNow = true; } else { this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode()); + if (mLowPingTimeoutMode.remove(account.getJid().toBareJid())) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": leaving low ping timeout mode"); + } } } - } else if (account.getStatus() == Account.State.OFFLINE) { + } + } else if (account.getStatus() == Account.State.OFFLINE) { + reconnectAccount(account, true, interactive); + } else if (account.getStatus() == Account.State.CONNECTING) { + 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) { + Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting (secondsSinceLast="+secondsSinceLastConnect+")"); + account.getXmppConnection().resetAttemptCount(false); reconnectAccount(account, true, interactive); - } else if (account.getStatus() == Account.State.CONNECTING) { - 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) { - Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting"); - account.getXmppConnection().resetAttemptCount(); - reconnectAccount(account, true, interactive); - } else if (discoTimeout < 0) { - account.getXmppConnection().sendDiscoTimeout(); - scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode()); - } else { - scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode()); - } + } else if (discoTimeout < 0) { + account.getXmppConnection().sendDiscoTimeout(); + scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode()); } else { - if (account.getXmppConnection().getTimeToNextAttempt() <= 0) { - reconnectAccount(account, true, interactive); - } + scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode()); } - } - if (mOnAccountUpdate != null) { - mOnAccountUpdate.onAccountUpdate(); - } - } - } - if (pingNow) { - final boolean listeners = checkListeners(); - for (Account account : pingCandidates) { - if (listeners - && Config.PUSH_MODE - && mPushManagementService.available(account)) { - account.getXmppConnection().waitForPush(); - cancelWakeUpCall(account.getUuid().hashCode()); } else { - account.getXmppConnection().sendPing(); - Log.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping (action=" + action + ",listeners="+Boolean.toString(listeners)+")"); - scheduleWakeUpCall(Config.PING_TIMEOUT, account.getUuid().hashCode()); + if (account.getXmppConnection().getTimeToNextAttempt() <= 0) { + reconnectAccount(account, true, interactive); + } } } } - if (wakeLock.isHeld()) { - try { - wakeLock.release(); - } catch (final RuntimeException ignored) { - } - } - return START_STICKY; + return pingNow; } public boolean isDataSaverDisabled() { @@ -746,15 +768,15 @@ public class XmppConnectionService extends Service { } private boolean manuallyChangePresence() { - return getPreferences().getBoolean("manually_change_presence", false); + return getPreferences().getBoolean(SettingsActivity.MANUALLY_CHANGE_PRESENCE, false); } private boolean treatVibrateAsSilent() { - return getPreferences().getBoolean("treat_vibrate_as_silent", false); + return getPreferences().getBoolean(SettingsActivity.TREAT_VIBRATE_AS_SILENT, false); } private boolean awayWhenScreenOff() { - return getPreferences().getBoolean("away_when_screen_off", false); + return getPreferences().getBoolean(SettingsActivity.AWAY_WHEN_SCREEN_IS_OFF, false); } private String getCompressPicturesPreference() { @@ -799,13 +821,13 @@ public class XmppConnectionService extends Service { } } - private void resetAllAttemptCounts(boolean reallyAll) { + private void resetAllAttemptCounts(boolean reallyAll, boolean retryImmediately) { Log.d(Config.LOGTAG, "resetting all attempt counts"); for (Account account : accounts) { if (account.hasErrorStatus() || reallyAll) { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { - connection.resetAttemptCount(); + connection.resetAttemptCount(retryImmediately); } } if (account.setShowErrorNotification(true)) { @@ -852,6 +874,11 @@ public class XmppConnectionService extends Service { this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext()); this.accounts = databaseBackend.getAccounts(); + if (!keepForegroundService() && databaseBackend.startTimeCountExceedsThreshold()) { + getPreferences().edit().putBoolean(SettingsActivity.KEEP_FOREGROUND_SERVICE,true).commit(); + Log.d(Config.LOGTAG,"number of restarts exceeds threshold. enabling foreground service"); + } + restoreFromDatabase(); getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver); @@ -882,10 +909,11 @@ public class XmppConnectionService extends Service { this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE); this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XmppConnectionService"); + toggleForegroundService(); updateUnreadCountBadge(); toggleScreenEventReceiver(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Config.PUSH_MODE) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { scheduleNextIdlePing(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { @@ -928,17 +956,21 @@ public class XmppConnectionService extends Service { } public void toggleForegroundService() { - if (getPreferences().getBoolean("keep_foreground_service", false)) { + if (keepForegroundService()) { startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, this.mNotificationService.createForegroundNotification()); } else { stopForeground(true); } } + private boolean keepForegroundService() { + return getPreferences().getBoolean(SettingsActivity.KEEP_FOREGROUND_SERVICE,false); + } + @Override public void onTaskRemoved(final Intent rootIntent) { super.onTaskRemoved(rootIntent); - if (!getPreferences().getBoolean("keep_foreground_service", false)) { + if (!keepForegroundService()) { this.logoutAndSave(false); } else { Log.d(Config.LOGTAG,"ignoring onTaskRemoved because foreground service is activated"); @@ -947,6 +979,7 @@ public class XmppConnectionService extends Service { private void logoutAndSave(boolean stop) { int activeAccounts = 0; + databaseBackend.clearStartTimeCounter(true); // regular swipes don't count towards restart counter for (final Account account : accounts) { if (account.getStatus() != Account.State.DISABLED) { activeAccounts++; @@ -967,13 +1000,6 @@ public class XmppConnectionService extends Service { } } - 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; AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); @@ -997,8 +1023,16 @@ public class XmppConnectionService extends Service { public XmppConnection createConnection(final Account account) { final SharedPreferences sharedPref = getPreferences(); - account.setResource(sharedPref.getString("resource", getString(R.string.default_resource)) - .toLowerCase(Locale.getDefault())); + String resource; + try { + resource = sharedPref.getString("resource", getString(R.string.default_resource)).toLowerCase(Locale.ENGLISH); + if (resource.trim().isEmpty()) { + throw new Exception(); + } + } catch (Exception e) { + resource = "conversations"; + } + account.setResource(resource); final XmppConnection connection = new XmppConnection(account, this); connection.setOnMessagePacketReceivedListener(this.mMessageParser); connection.setOnStatusChangedListener(this.statusListener); @@ -1602,7 +1636,7 @@ public class XmppConnectionService extends Service { ); } } - this.databaseBackend.updateConversation(conversation); + updateConversation(conversation); this.conversations.remove(conversation); updateConversationUi(); } @@ -1988,10 +2022,6 @@ public class XmppConnectionService extends Service { if (connection.getFeatures().csi()) { connection.sendInactive(); } - if (Config.PUSH_MODE && mPushManagementService.available(account)) { - connection.waitForPush(); - cancelWakeUpCall(account.getUuid().hashCode()); - } } } } @@ -2029,11 +2059,11 @@ public class XmppConnectionService extends Service { final MucOptions mucOptions = conversation.getMucOptions(); final Jid joinJid = mucOptions.getSelf().getFullJid(); Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString()); - PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE); + PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous()); 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("password").setContent(mucOptions.getPassword()); } if (mucOptions.mamSupport()) { @@ -2101,10 +2131,7 @@ public class XmppConnectionService extends Service { if ("item".equals(child.getName())) { MucOptions.User user = AbstractParser.parseItem(conversation,child); if (!user.realJidMatchesAccount()) { - conversation.getMucOptions().addUser(user); - getAvatarService().clear(conversation); - updateMucRosterUi(); - updateConversationUi(); + conversation.getMucOptions().updateUser(user); } } } @@ -2114,6 +2141,9 @@ public class XmppConnectionService extends Service { ++i; if (i >= affiliations.length) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": retrieved members for "+conversation.getJid().toBareJid()+": "+conversation.getMucOptions().getMembers()); + getAvatarService().clear(conversation); + updateMucRosterUi(); + updateConversationUi(); } } }; @@ -2132,7 +2162,7 @@ public class XmppConnectionService extends Service { } pushBookmarks(conversation.getAccount()); } - databaseBackend.updateConversation(conversation); + updateConversation(conversation); joinMuc(conversation); } } @@ -2852,8 +2882,13 @@ public class XmppConnectionService extends Service { } } - public void updateConversation(Conversation conversation) { - this.databaseBackend.updateConversation(conversation); + public void updateConversation(final Conversation conversation) { + mDatabaseExecutor.execute(new Runnable() { + @Override + public void run() { + databaseBackend.updateConversation(conversation); + } + }); } private void reconnectAccount(final Account account, final boolean force, final boolean interactive) { @@ -2862,8 +2897,6 @@ public class XmppConnectionService extends Service { if (connection == null) { connection = createConnection(account); account.setXmppConnection(connection); - } else { - connection.interrupt(); } if (!account.isOptionSet(Account.OPTION_DISABLED)) { if (!force) { @@ -2872,10 +2905,11 @@ public class XmppConnectionService extends Service { Thread thread = new Thread(connection); connection.setInteractive(interactive); connection.prepareNewConnection(); + connection.interrupt(); thread.start(); scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode()); } else { - disconnect(account, force); + disconnect(account, force || account.getTrueStatus().isError()); account.getRoster().clearPresences(); connection.resetEverything(); account.getAxolotlService().resetBrokenness(); @@ -2918,6 +2952,10 @@ public class XmppConnectionService extends Service { } public Message markMessage(final Account account, final Jid recipient, final String uuid, final int status) { + return markMessage(account, recipient, uuid, status, null); + } + + public Message markMessage(final Account account, final Jid recipient, final String uuid, final int status, String errorMessage) { if (uuid == null) { return null; } @@ -2925,7 +2963,7 @@ public class XmppConnectionService extends Service { if (conversation.getJid().toBareJid().equals(recipient) && conversation.getAccount() == account) { final Message message = conversation.findSentMessageWithUuidOrRemoteId(uuid); if (message != null) { - markMessage(message, status); + markMessage(message, status, errorMessage); } return message; } @@ -2948,11 +2986,17 @@ public class XmppConnectionService extends Service { } public void markMessage(Message message, int status) { + markMessage(message, status, null); + } + + + public void markMessage(Message message, int status, String errorMessage) { if (status == Message.STATUS_SEND_FAILED && (message.getStatus() == Message.STATUS_SEND_RECEIVED || message .getStatus() == Message.STATUS_SEND_DISPLAYED)) { return; } + message.setErrorMessage(errorMessage); message.setStatus(status); databaseBackend.updateMessage(message); updateConversationUi(); @@ -3564,6 +3608,68 @@ public class XmppConnectionService extends Service { conversation.setBookmark(bookmark); } + public void clearStartTimeCounter() { + mDatabaseExecutor.execute(new Runnable() { + @Override + public void run() { + databaseBackend.clearStartTimeCounter(false); + } + }); + } + + public boolean verifyFingerprints(Contact contact, List<XmppUri.Fingerprint> fingerprints) { + boolean needsRosterWrite = false; + boolean performedVerification = false; + final AxolotlService axolotlService = contact.getAccount().getAxolotlService(); + for(XmppUri.Fingerprint fp : fingerprints) { + if (fp.type == XmppUri.FingerprintType.OTR) { + performedVerification |= contact.addOtrFingerprint(fp.fingerprint); + needsRosterWrite |= performedVerification; + } else if (fp.type == XmppUri.FingerprintType.OMEMO) { + String fingerprint = "05"+fp.fingerprint.replaceAll("\\s",""); + FingerprintStatus fingerprintStatus = axolotlService.getFingerprintTrust(fingerprint); + if (fingerprintStatus != null) { + if (!fingerprintStatus.isVerified()) { + performedVerification = true; + axolotlService.setFingerprintTrust(fingerprint,fingerprintStatus.toVerified()); + } + } else { + axolotlService.preVerifyFingerprint(contact,fingerprint); + } + } + } + if (needsRosterWrite) { + syncRosterToDisk(contact.getAccount()); + } + return performedVerification; + } + + public boolean verifyFingerprints(Account account, List<XmppUri.Fingerprint> fingerprints) { + final AxolotlService axolotlService = account.getAxolotlService(); + boolean verifiedSomething = false; + for(XmppUri.Fingerprint fp : fingerprints) { + if (fp.type == XmppUri.FingerprintType.OMEMO) { + String fingerprint = "05"+fp.fingerprint.replaceAll("\\s",""); + Log.d(Config.LOGTAG,"trying to verify own fp="+fingerprint); + FingerprintStatus fingerprintStatus = axolotlService.getFingerprintTrust(fingerprint); + if (fingerprintStatus != null) { + if (!fingerprintStatus.isVerified()) { + axolotlService.setFingerprintTrust(fingerprint,fingerprintStatus.toVerified()); + verifiedSomething = true; + } + } else { + axolotlService.preVerifyFingerprint(account,fingerprint); + verifiedSomething = true; + } + } + } + return verifiedSomething; + } + + public boolean blindTrustBeforeVerification() { + return getPreferences().getBoolean(SettingsActivity.BLIND_TRUST_BEFORE_VERIFICATION, true); + } + public interface OnMamPreferencesFetched { void onPreferencesFetched(Element prefs); void onPreferencesFetchFailed(); diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 7791372a..42eb49fb 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -380,7 +380,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers MenuItem invite = menu.findItem(R.id.invite); startConversation.setVisible(true); if (contact != null) { - showContactDetails.setVisible(true); + showContactDetails.setVisible(!contact.isSelf()); } if (user.getRole() == MucOptions.Role.NONE) { invite.setVisible(true); diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 08128094..296a10c2 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -36,25 +36,27 @@ import java.security.cert.X509Certificate; import java.util.List; import eu.siacs.conversations.Config; +import eu.siacs.conversations.OmemoActivity; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.ListItem; -import eu.siacs.conversations.entities.Presence; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; -public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated { +public class ContactDetailsActivity extends OmemoActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated { public static final String ACTION_VIEW_CONTACT = "view_contact"; private Contact contact; @@ -445,14 +447,12 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } } if (Config.supportOmemo()) { - 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); - } - }); + for (final XmppAxolotlSession session : contact.getAccount().getAxolotlService().findSessionsForContact(contact)) { + if (!session.getTrust().isCompromised()) { + boolean highlight = session.getFingerprint().equals(messageFingerprint); + hasKeys = true; + addFingerprintRow(keys, session, highlight); + } } } if (Config.supportOpenPgp() && contact.getPgpKeyId() != 0) { @@ -508,40 +508,6 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } } - private void onOmemoKeyClicked(Account account, String fingerprint) { - final XmppAxolotlSession.Trust trust = account.getAxolotlService().getFingerprintTrust(fingerprint); - if (Config.X509_VERIFICATION && 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); @@ -562,15 +528,17 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd builder.create().show(); } - @Override public void onBackendConnected() { - if ((accountJid != null) && (contactJid != null)) { - Account account = xmppConnectionService - .findAccountByJid(accountJid); + if (accountJid != null && contactJid != null) { + Account account = xmppConnectionService.findAccountByJid(accountJid); if (account == null) { return; } this.contact = account.getRoster().getContact(contactJid); + if (mPendingFingerprintVerificationUri != null) { + processFingerprintVerification(mPendingFingerprintVerificationUri); + mPendingFingerprintVerificationUri = null; + } populateView(); } } @@ -579,4 +547,15 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd public void onKeyStatusUpdated(AxolotlService.FetchStatus report) { refreshUi(); } + + @Override + protected void processFingerprintVerification(XmppUri uri) { + if (contact != null && contact.getJid().toBareJid().equals(uri.getJid()) && uri.hasFingerprints()) { + if (xmppConnectionService.verifyFingerprints(contact,uri.getFingerprints())) { + Toast.makeText(this,R.string.verified_fingerprints,Toast.LENGTH_SHORT).show(); + } + } else { + Toast.makeText(this,R.string.invalid_barcode,Toast.LENGTH_SHORT).show(); + } + } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index b2c4efda..d298aa28 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -49,6 +49,7 @@ import de.timroes.android.listview.EnhancedListView; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; @@ -64,6 +65,7 @@ import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.ui.adapter.ConversationAdapter; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -119,6 +121,7 @@ public class ConversationActivity extends XmppActivity private boolean mActivityPaused = false; private AtomicBoolean mRedirected = new AtomicBoolean(false); private Pair<Integer, Intent> mPostponedActivityResult; + private boolean mUnprocessedNewIntent = false; public Conversation getSelectedConversation() { return this.mSelectedConversation; @@ -374,7 +377,7 @@ public class ConversationActivity extends XmppActivity } public void sendReadMarkerIfNecessary(final Conversation conversation) { - if (!mActivityPaused && conversation != null) { + if (!mActivityPaused && !mUnprocessedNewIntent && conversation != null) { xmppConnectionService.sendReadMarker(conversation); } } @@ -549,7 +552,7 @@ public class ConversationActivity extends XmppActivity public void attachFile(final int attachmentChoice) { if (attachmentChoice != ATTACHMENT_CHOICE_LOCATION) { - if (!hasStoragePermission(attachmentChoice)) { + if (!Config.ONLY_INTERNAL_STORAGE && !hasStoragePermission(attachmentChoice)) { return; } } @@ -612,10 +615,8 @@ public class ConversationActivity extends XmppActivity @Override public void onClick(DialogInterface dialog, int which) { - conversation - .setNextEncryption(Message.ENCRYPTION_NONE); - xmppConnectionService.databaseBackend - .updateConversation(conversation); + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + xmppConnectionService.updateConversation(conversation); selectPresenceToAttachFile(attachmentChoice, Message.ENCRYPTION_NONE); } }); @@ -648,7 +649,7 @@ public class ConversationActivity extends XmppActivity } public void startDownloadable(Message message) { - if (!hasStoragePermission(ConversationActivity.REQUEST_START_DOWNLOAD)) { + if (!Config.ONLY_INTERNAL_STORAGE && !hasStoragePermission(ConversationActivity.REQUEST_START_DOWNLOAD)) { this.mPendingDownloadableMessage = message; return; } @@ -889,7 +890,7 @@ public class ConversationActivity extends XmppActivity conversation.setNextEncryption(Message.ENCRYPTION_NONE); break; } - xmppConnectionService.databaseBackend.updateConversation(conversation); + xmppConnectionService.updateConversation(conversation); fragment.updateChatMsgHint(); invalidateOptionsMenu(); refreshUi(); @@ -948,8 +949,7 @@ public class ConversationActivity extends XmppActivity till = System.currentTimeMillis() + (durations[which] * 1000); } conversation.setMutedTill(till); - ConversationActivity.this.xmppConnectionService.databaseBackend - .updateConversation(conversation); + ConversationActivity.this.xmppConnectionService.updateConversation(conversation); updateConversationList(); ConversationActivity.this.mConversationFragment.updateMessages(); invalidateOptionsMenu(); @@ -960,7 +960,7 @@ public class ConversationActivity extends XmppActivity public void unmuteConversation(final Conversation conversation) { conversation.setMutedTill(0); - this.xmppConnectionService.databaseBackend.updateConversation(conversation); + this.xmppConnectionService.updateConversation(conversation); updateConversationList(); ConversationActivity.this.mConversationFragment.updateMessages(); invalidateOptionsMenu(); @@ -1090,6 +1090,7 @@ public class ConversationActivity extends XmppActivity protected void onNewIntent(final Intent intent) { if (intent != null && ACTION_VIEW_CONVERSATION.equals(intent.getAction())) { mOpenConversation = null; + mUnprocessedNewIntent = true; if (xmppConnectionServiceBound) { handleViewConversationIntent(intent); intent.setAction(Intent.ACTION_MAIN); @@ -1128,6 +1129,7 @@ public class ConversationActivity extends XmppActivity } this.mActivityPaused = false; + if (!isConversationsOverviewVisable() || !isConversationsOverviewHideable()) { sendReadMarkerIfNecessary(getSelectedConversation()); } @@ -1292,6 +1294,7 @@ public class ConversationActivity extends XmppActivity this.mConversationFragment.appendText(text); } hideConversationsOverview(); + mUnprocessedNewIntent = false; openConversation(); if (mContentView instanceof SlidingPaneLayout) { updateActionBarTitle(true); //fixes bug where slp isn't properly closed yet @@ -1302,6 +1305,8 @@ public class ConversationActivity extends XmppActivity startDownloadable(message); } } + } else { + mUnprocessedNewIntent = false; } } @@ -1452,7 +1457,8 @@ public class ConversationActivity extends XmppActivity } private long getMaxHttpUploadSize(Conversation conversation) { - return conversation.getAccount().getXmppConnection().getFeatures().getMaxHttpUploadSize(); + final XmppConnection connection = conversation.getAccount().getXmppConnection(); + return connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize(); } private void setNeverAskForBatteryOptimizationsAgain() { @@ -1675,8 +1681,8 @@ public class ConversationActivity extends XmppActivity AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService(); final List<Jid> targets = axolotlService.getCryptoTargets(mSelectedConversation); boolean hasUnaccepted = !mSelectedConversation.getAcceptedCryptoTargets().containsAll(targets); - boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED).isEmpty(); - boolean hasUndecidedContacts = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, targets).isEmpty(); + boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided()).isEmpty(); + boolean hasUndecidedContacts = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), targets).isEmpty(); boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty(); boolean hasNoTrustedKeys = axolotlService.anyTargetHasNoTrustedKeys(targets); if(hasUndecidedOwn || hasUndecidedContacts || hasPendingKeys || hasNoTrustedKeys || hasUnaccepted) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 3490a712..d9554024 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -62,6 +62,7 @@ import eu.siacs.conversations.ui.XmppActivity.OnValueEdited; import eu.siacs.conversations.ui.adapter.MessageAdapter; import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked; import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked; +import eu.siacs.conversations.ui.widget.ListSelectionManager; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.XmppConnection; @@ -536,6 +537,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 selectText = menu.findItem(R.id.select_text); MenuItem retryDecryption = menu.findItem(R.id.retry_decryption); MenuItem correctMessage = menu.findItem(R.id.correct_message); MenuItem shareWith = menu.findItem(R.id.share_with); @@ -544,16 +546,19 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa MenuItem downloadFile = menu.findItem(R.id.download_file); MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission); MenuItem deleteFile = menu.findItem(R.id.delete_file); + MenuItem showErrorMessage = menu.findItem(R.id.show_error_message); if (!treatAsFile && !GeoHelper.isGeoUri(m.getBody()) && m.treatAsDownloadable() != Message.Decision.MUST) { copyText.setVisible(true); + selectText.setVisible(ListSelectionManager.isSupported()); } if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { retryDecryption.setVisible(true); } if (relevantForCorrection.getType() == Message.TYPE_TEXT - && relevantForCorrection.isLastCorrectableMessage()) { + && relevantForCorrection.isLastCorrectableMessage() + && (m.getConversation().getMucOptions().nonanonymous() || m.getConversation().getMode() == Conversation.MODE_SINGLE)) { correctMessage.setVisible(true); } if (treatAsFile || (GeoHelper.isGeoUri(m.getBody()))) { @@ -586,6 +591,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa deleteFile.setTitle(activity.getString(R.string.delete_x_file, UIHelper.getFileDescriptionString(activity, m))); } } + if (m.getStatus() == Message.STATUS_SEND_FAILED && m.getErrorMessage() != null) { + showErrorMessage.setVisible(true); + } } } @@ -598,6 +606,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa case R.id.copy_text: copyText(selectedMessage); return true; + case R.id.select_text: + selectText(selectedMessage); + return true; case R.id.correct_message: correctMessage(selectedMessage); return true; @@ -619,11 +630,22 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa case R.id.delete_file: deleteFile(selectedMessage); return true; + case R.id.show_error_message: + showErrorMessage(selectedMessage); + return true; default: return super.onContextItemSelected(item); } } + private void showErrorMessage(final Message message) { + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(R.string.error_message); + builder.setMessage(message.getErrorMessage()); + builder.setPositiveButton(R.string.confirm,null); + builder.create().show(); + } + private void shareWith(Message message) { Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); @@ -632,8 +654,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa shareIntent.setType("text/plain"); } else { shareIntent.putExtra(Intent.EXTRA_STREAM, - activity.xmppConnectionService.getFileBackend() - .getJingleFileUri(message)); + activity.xmppConnectionService.getFileBackend().getJingleFileUri(message)); shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); String mime = message.getMimeType(); if (mime == null) { @@ -650,13 +671,31 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } private void copyText(Message message) { - if (activity.copyTextToClipboard(message.getMergedBody(), + if (activity.copyTextToClipboard(message.getMergedBody().toString(), R.string.message_text)) { Toast.makeText(activity, R.string.message_copied_to_clipboard, Toast.LENGTH_SHORT).show(); } } + private void selectText(Message message) { + final int index; + synchronized (this.messageList) { + index = this.messageList.indexOf(message); + } + if (index >= 0) { + final int first = this.messagesView.getFirstVisiblePosition(); + final int last = first + this.messagesView.getChildCount(); + if (index >= first && index < last) { + final View view = this.messagesView.getChildAt(index - first); + final TextView messageBody = this.messageListAdapter.getMessageBody(view); + if (messageBody != null) { + ListSelectionManager.startSelection(messageBody); + } + } + } + } + private void deleteFile(Message message) { if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) { message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); @@ -877,15 +916,15 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private void updateSnackBar(final Conversation conversation) { final Account account = conversation.getAccount(); - final Contact contact = conversation.getContact(); final int mode = conversation.getMode(); + final Contact contact = mode == Conversation.MODE_SINGLE ? conversation.getContact() : null; if (account.getStatus() == Account.State.DISABLED) { showSnackbar(R.string.this_account_is_disabled, R.string.enable, this.mEnableAccountListener); } else if (conversation.isBlocked()) { showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener); - } else if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { + } else if (contact != null && !contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { showSnackbar(R.string.contact_added_you, R.string.add_back, this.mAddBackClickListener); - } else if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { + } else if (contact != null && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { showSnackbar(R.string.contact_asks_for_presence_subscription, R.string.allow, this.mAllowPresenceSubscription); } else if (mode == Conversation.MODE_MULTI && !conversation.getMucOptions().online() @@ -1238,8 +1277,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa int which) { conversation .setNextEncryption(Message.ENCRYPTION_NONE); - xmppService.databaseBackend - .updateConversation(conversation); + xmppService.updateConversation(conversation); message.setEncryption(Message.ENCRYPTION_NONE); xmppService.sendMessage(message); messageSent(); @@ -1267,8 +1305,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa conversation .setNextEncryption(Message.ENCRYPTION_NONE); message.setEncryption(Message.ENCRYPTION_NONE); - xmppService.databaseBackend - .updateConversation(conversation); + xmppService.updateConversation(conversation); xmppService.sendMessage(message); messageSent(); } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index cc178179..f48e8a48 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -35,23 +35,25 @@ import android.widget.TableRow; import android.widget.TextView; import android.widget.Toast; -import android.util.Log; - import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import eu.siacs.conversations.Config; +import eu.siacs.conversations.OmemoActivity; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.services.BarcodeProvider; import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.XmppConnection; @@ -61,20 +63,23 @@ import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.pep.Avatar; -public class EditAccountActivity extends XmppActivity implements OnAccountUpdate, +public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched { + private static final int REQUEST_DATA_SAVER = 0x37af244; private AutoCompleteTextView mAccountJid; private EditText mPassword; private EditText mPasswordConfirm; private CheckBox mRegisterNew; private Button mCancelButton; private Button mSaveButton; - private Button mDisableBatterOptimizations; + private Button mDisableOsOptimizationsButton; + private TextView mDisableOsOptimizationsHeadline; + private TextView getmDisableOsOptimizationsBody; private TableLayout mMoreTable; private LinearLayout mStats; - private RelativeLayout mBatteryOptimizations; + private RelativeLayout mOsOptimizations; private TextView mServerInfoSm; private TextView mServerInfoRosterVersion; private TextView mServerInfoCarbons; @@ -249,6 +254,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private TableRow mPushRow; private String mSavedInstanceAccount; private boolean mSavedInstanceInit = false; + private Button mClearDevicesButton; public void refreshUiReal() { invalidateOptionsMenu(); @@ -371,13 +377,24 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { + public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_BATTERY_OP) { + if (requestCode == REQUEST_BATTERY_OP || requestCode == REQUEST_DATA_SAVER) { updateAccountInformation(mAccount == null); } } + @Override + protected void processFingerprintVerification(XmppUri uri) { + if (mAccount != null && mAccount.getJid().toBareJid().equals(uri.getJid()) && uri.hasFingerprints()) { + if (xmppConnectionService.verifyFingerprints(mAccount,uri.getFingerprints())) { + Toast.makeText(this,R.string.verified_fingerprints,Toast.LENGTH_SHORT).show(); + } + } else { + Toast.makeText(this,R.string.invalid_barcode,Toast.LENGTH_SHORT).show(); + } + } + protected void updateSaveButton() { boolean accountInfoEdited = accountInfoEdited(); @@ -472,21 +489,10 @@ 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); - try { - startActivityForResult(intent, REQUEST_BATTERY_OP); - } catch (ActivityNotFoundException e) { - Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); - } - } - }); + this.mOsOptimizations = (RelativeLayout) findViewById(R.id.os_optimization); + this.mDisableOsOptimizationsButton = (Button) findViewById(R.id.os_optimization_disable); + this.mDisableOsOptimizationsHeadline = (TextView) findViewById(R.id.os_optimization_headline); + this.getmDisableOsOptimizationsBody = (TextView) findViewById(R.id.os_optimization_body); 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); @@ -511,6 +517,13 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mNamePort = (LinearLayout) findViewById(R.id.name_port); this.mHostname = (EditText) findViewById(R.id.hostname); this.mHostname.addTextChangedListener(mTextWatcher); + this.mClearDevicesButton = (Button) findViewById(R.id.clear_devices); + this.mClearDevicesButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + showWipePepDialog(); + } + }); this.mPort = (EditText) findViewById(R.id.port); this.mPort.setText("5222"); this.mPort.addTextChangedListener(mTextWatcher); @@ -549,12 +562,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate 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 showPassword = menu.findItem(R.id.action_show_password); - 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); final MenuItem changePresence = menu.findItem(R.id.action_change_presence); + final MenuItem share = menu.findItem(R.id.action_share); renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null); + share.setVisible(mAccount != null && !mInitMode); + if (mAccount != null && mAccount.isOnlineAndConnected()) { if (!mAccount.getXmppConnection().getFeatures().blocking()) { showBlocklist.setVisible(false); @@ -563,17 +578,12 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate changePassword.setVisible(false); } mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam()); - Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds(); - if (otherDevices == null || otherDevices.isEmpty() || !Config.supportOmemo()) { - clearDevices.setVisible(false); - } changePresence.setVisible(manuallyChangePresence()); } else { showQrCode.setVisible(false); showBlocklist.setVisible(false); showMoreInfo.setVisible(false); changePassword.setVisible(false); - clearDevices.setVisible(false); mamPrefs.setVisible(false); changePresence.setVisible(false); } @@ -645,7 +655,6 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate super.onSaveInstanceState(savedInstanceState); } - @Override protected void onBackendConnected() { boolean init = true; if (mSavedInstanceAccount != null) { @@ -670,6 +679,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mPassword.requestFocus(); } } + if (mPendingFingerprintVerificationUri != null) { + processFingerprintVerification(mPendingFingerprintVerificationUri); + mPendingFingerprintVerificationUri = null; + } updateAccountInformation(init); } @@ -710,15 +723,21 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate case R.id.action_server_info_show_more: changeMoreTableVisibility(!item.isChecked()); break; + case R.id.action_share_barcode: + shareBarcode(); + break; + case R.id.action_share_http: + shareLink(true); + break; + case R.id.action_share_uri: + shareLink(false); + break; case R.id.action_change_password_on_server: gotoChangePassword(null); 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; @@ -732,6 +751,27 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate return super.onOptionsItemSelected(item); } + private void shareLink(boolean http) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + String text; + if (http) { + text = "https://conversations.im/i/"+mAccount.getJid().toBareJid().toString(); + } else { + text = mAccount.getShareableUri(); + } + intent.putExtra(Intent.EXTRA_TEXT,text); + startActivity(Intent.createChooser(intent, getText(R.string.share_with))); + } + + private void shareBarcode() { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_STREAM,BarcodeProvider.getUriForAccount(this,mAccount)); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setType("image/png"); + startActivity(Intent.createChooser(intent, getText(R.string.share_with))); + } + private void changeMoreTableVisibility(boolean visible) { mMoreTable.setVisibility(visible ? View.VISIBLE : View.GONE); } @@ -796,8 +836,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) { Features features = this.mAccount.getXmppConnection().getFeatures(); this.mStats.setVisibility(View.VISIBLE); - boolean showOptimizingWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery(); - this.mBatteryOptimizations.setVisibility(showOptimizingWarning ? View.VISIBLE : View.GONE); + boolean showBatteryWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery(); + boolean showDataSaverWarning = isAffectedByDataSaver(); + showOsOptimizationWarning(showBatteryWarning,showDataSaverWarning); this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection() .getLastSessionEstablished())); if (features.rosterVersioning()) { @@ -893,13 +934,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate @Override public void onClick(final View v) { - - if (copyTextToClipboard(ownAxolotlFingerprint.substring(2), R.string.omemo_fingerprint)) { - Toast.makeText( - EditAccountActivity.this, - R.string.toast_message_omemo_fingerprint, - Toast.LENGTH_SHORT).show(); - } + copyOmemoFingerprint(ownAxolotlFingerprint); } }); if (Config.SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON) { @@ -919,15 +954,21 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } boolean hasKeys = false; keys.removeAllViews(); - for (final String fingerprint : mAccount.getAxolotlService().getFingerprintsForOwnSessions()) { - if (ownAxolotlFingerprint.equals(fingerprint)) { - continue; + for(XmppAxolotlSession session : mAccount.getAxolotlService().findOwnSessions()) { + if (!session.getTrust().isCompromised()) { + boolean highlight = session.getFingerprint().equals(messageFingerprint); + addFingerprintRow(keys,session,highlight); + hasKeys = true; } - boolean highlight = fingerprint.equals(messageFingerprint); - hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight, null); } if (hasKeys && Config.supportOmemo()) { keysCard.setVisibility(View.VISIBLE); + Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds(); + if (otherDevices == null || otherDevices.isEmpty()) { + mClearDevicesButton.setVisibility(View.GONE); + } else { + mClearDevicesButton.setVisibility(View.VISIBLE); + } } else { keysCard.setVisibility(View.GONE); } @@ -956,6 +997,45 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } } + private void showOsOptimizationWarning(boolean showBatteryWarning, boolean showDataSaverWarning) { + this.mOsOptimizations.setVisibility(showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE); + if (showDataSaverWarning) { + this.mDisableOsOptimizationsHeadline.setText(R.string.data_saver_enabled); + this.getmDisableOsOptimizationsBody.setText(R.string.data_saver_enabled_explained); + this.mDisableOsOptimizationsButton.setText(R.string.allow); + this.mDisableOsOptimizationsButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS); + Uri uri = Uri.parse("package:"+getPackageName()); + intent.setData(uri); + try { + startActivityForResult(intent, REQUEST_DATA_SAVER); + } catch (ActivityNotFoundException e) { + Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_data_saver, Toast.LENGTH_SHORT).show(); + } + } + }); + } else if (showBatteryWarning) { + this.mDisableOsOptimizationsButton.setText(R.string.disable); + this.mDisableOsOptimizationsHeadline.setText(R.string.battery_optimizations_enabled); + this.getmDisableOsOptimizationsBody.setText(R.string.battery_optimizations_enabled_explained); + this.mDisableOsOptimizationsButton.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); + try { + startActivityForResult(intent, REQUEST_BATTERY_OP); + } catch (ActivityNotFoundException e) { + Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); + } + } + }); + } + } + public void showRegenerateAxolotlKeyDialog() { Builder builder = new Builder(this); builder.setTitle("Regenerate Key"); diff --git a/src/main/java/eu/siacs/conversations/ui/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/EditMessage.java index e3841d1d..e609d08e 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/EditMessage.java @@ -1,7 +1,11 @@ package eu.siacs.conversations.ui; import android.content.Context; +import android.os.Build; import android.os.Handler; +import android.text.Editable; +import android.text.InputFilter; +import android.text.Spanned; import android.util.AttributeSet; import android.view.KeyEvent; import android.widget.EditText; @@ -89,4 +93,36 @@ public class EditMessage extends EditText { boolean onTabPressed(boolean repeated); } + private static final InputFilter SPAN_FILTER = new InputFilter() { + + @Override + public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { + return source instanceof Spanned ? source.toString() : source; + } + }; + + @Override + public boolean onTextContextMenuItem(int id) { + if (id == android.R.id.paste) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return super.onTextContextMenuItem(android.R.id.pasteAsPlainText); + } else { + Editable editable = getEditableText(); + InputFilter[] filters = editable.getFilters(); + InputFilter[] tempFilters = new InputFilter[filters != null ? filters.length + 1 : 1]; + if (filters != null) { + System.arraycopy(filters, 0, tempFilters, 1, filters.length); + } + tempFilters[0] = SPAN_FILTER; + editable.setFilters(tempFilters); + try { + return super.onTextContextMenuItem(id); + } finally { + editable.setFilters(filters); + } + } + } else { + return super.onTextContextMenuItem(id); + } + } } diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java index 5dc38a9d..bc80f90a 100644 --- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java @@ -38,6 +38,12 @@ import eu.siacs.conversations.xmpp.jid.Jid; public class SettingsActivity extends XmppActivity implements OnSharedPreferenceChangeListener { + public static final String KEEP_FOREGROUND_SERVICE = "enable_foreground_service"; + public static final String AWAY_WHEN_SCREEN_IS_OFF = "away_when_screen_off"; + public static final String TREAT_VIBRATE_AS_SILENT = "treat_vibrate_as_silent"; + public static final String MANUALLY_CHANGE_PRESENCE = "manually_change_presence"; + public static final String BLIND_TRUST_BEFORE_VERIFICATION = "btbv"; + public static final int REQUEST_WRITE_LOGS = 0xbf8701; private SettingsFragment mSettingsFragment; @@ -301,10 +307,10 @@ public class SettingsActivity extends XmppActivity implements final List<String> resendPresence = Arrays.asList( "confirm_messages", "xa_on_silent_mode", - "away_when_screen_off", + AWAY_WHEN_SCREEN_IS_OFF, "allow_message_correction", - "treat_vibrate_as_silent", - "manually_change_presence", + TREAT_VIBRATE_AS_SILENT, + MANUALLY_CHANGE_PRESENCE, "last_activity"); if (name.equals("resource")) { String resource = preferences.getString("resource", "mobile") @@ -322,15 +328,18 @@ public class SettingsActivity extends XmppActivity implements } } } - } else if (name.equals("keep_foreground_service")) { + } else if (name.equals(KEEP_FOREGROUND_SERVICE)) { + boolean foreground_service = preferences.getBoolean(KEEP_FOREGROUND_SERVICE,false); + if (!foreground_service) { + xmppConnectionService.clearStartTimeCounter(); + } xmppConnectionService.toggleForegroundService(); } else if (resendPresence.contains(name)) { if (xmppConnectionServiceBound) { - if (name.equals("away_when_screen_off") - || name.equals("manually_change_presence")) { + if (name.equals(AWAY_WHEN_SCREEN_IS_OFF) || name.equals(MANUALLY_CHANGE_PRESENCE)) { xmppConnectionService.toggleScreenEventReceiver(); } - if (name.equals("manually_change_presence") && !noAccountUsesPgp()) { + if (name.equals(MANUALLY_CHANGE_PRESENCE) && !noAccountUsesPgp()) { Toast.makeText(this, R.string.republish_pgp_keys, Toast.LENGTH_LONG).show(); } xmppConnectionService.refreshAllPresences(); diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index ec5559ae..25ce50eb 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -397,11 +397,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } @SuppressLint("InflateParams") - protected void showCreateContactDialog(final String prefilledJid, final String fingerprint) { + protected void showCreateContactDialog(final String prefilledJid, final Invite invite) { EnterJidDialog dialog = new EnterJidDialog( this, mKnownHosts, mActivatedAccounts, getString(R.string.create_contact), getString(R.string.create), - prefilledJid, null, fingerprint == null + prefilledJid, null, invite == null || !invite.hasFingerprints() ); dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() { @@ -420,9 +420,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU if (contact.showInRoster()) { throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists)); } else { - contact.addOtrFingerprint(fingerprint); xmppConnectionService.createContact(contact); - switchToConversation(contact); + if (invite != null && invite.hasFingerprints()) { + xmppConnectionService.verifyFingerprints(contact,invite.getFingerprints()); + } + switchToConversation(contact, invite == null ? null : invite.getBody()); return true; } } @@ -561,11 +563,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU return xmppConnectionService.findAccountByJid(jid); } - protected void switchToConversation(Contact contact) { + protected void switchToConversation(Contact contact, String body) { Conversation conversation = xmppConnectionService .findOrCreateConversation(contact.getAccount(), contact.getJid(), false); - switchToConversation(conversation); + switchToConversation(conversation, body, false); } public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) { @@ -624,7 +626,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU showCreateConferenceDialog(); return true; case R.id.action_scan_qr_code: - new IntentIntegrator(this).initiateScan(); + new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE")); return true; case R.id.action_hide_offline: mHideOfflineContacts = !item.isChecked(); @@ -842,28 +844,33 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } private boolean handleJid(Invite invite) { + Account account = xmppConnectionService.findAccountByJid(invite.getJid()); + if (account != null && !account.isOptionSet(Account.OPTION_DISABLED) && invite.hasFingerprints()) { + if (xmppConnectionService.verifyFingerprints(account,invite.getFingerprints())) { + switchToAccount(account); + finish(); + return true; + } + } List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid()); if (invite.isMuc()) { Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid()); if (muc != null) { - switchToConversation(muc); + switchToConversation(muc,invite.getBody(),false); return true; } else { showJoinConferenceDialog(invite.getJid().toBareJid().toString()); return false; } } else if (contacts.size() == 0) { - showCreateContactDialog(invite.getJid().toString(), invite.getFingerprint()); + showCreateContactDialog(invite.getJid().toString(), invite); return false; } else if (contacts.size() == 1) { Contact contact = contacts.get(0); - if (invite.getFingerprint() != null) { - if (contact.addOtrFingerprint(invite.getFingerprint())) { - Log.d(Config.LOGTAG, "added new fingerprint"); - xmppConnectionService.syncRosterToDisk(contact.getAccount()); - } + if (invite.hasFingerprints()) { + xmppConnectionService.verifyFingerprints(contact,invite.getFingerprints()); } - switchToConversation(contact); + switchToConversation(contact,invite.getBody()); return true; } else { if (mMenuSearchView != null) { @@ -1049,10 +1056,14 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU activity.conference_context_id = acmi.position; } else if (mResContextMenu == R.menu.contact_context) { activity.contact_context_id = acmi.position; - final Blockable contact = (Contact) activity.contacts.get(acmi.position); + final Contact contact = (Contact) activity.contacts.get(acmi.position); final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock); + final MenuItem showContactDetailsItem = menu.findItem(R.id.context_contact_details); + if (contact.isSelf()) { + showContactDetailsItem.setVisible(false); + } XmppConnection xmpp = contact.getAccount().getXmppConnection(); - if (xmpp != null && xmpp.getFeatures().blocking()) { + if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) { if (contact.isBlocked()) { blockUnblockItem.setTitle(R.string.unblock_contact); } else { diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index cc4ba7b2..1f7951e4 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -1,7 +1,12 @@ package eu.siacs.conversations.ui; +import android.app.ActionBar; import android.content.Intent; import android.os.Bundle; +import android.util.Log; +import android.view.Gravity; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; @@ -10,24 +15,31 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; + import org.whispersystems.libaxolotl.IdentityKey; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.OmemoActivity; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.AxolotlService; -import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; +import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; -public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated { +public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdated { private List<Jid> contactJids; private Account mAccount; @@ -61,6 +73,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate finish(); } }; + private Toast mUseCameraHintToast = null; @Override protected void refreshUiReal() { @@ -99,6 +112,61 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate } } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.trust_keys, menu); + mUseCameraHintToast = Toast.makeText(this,R.string.use_camera_icon_to_scan_barcode,Toast.LENGTH_LONG); + ActionBar actionBar = getActionBar(); + mUseCameraHintToast.setGravity(Gravity.TOP | Gravity.END, 0 ,actionBar == null ? 0 : actionBar.getHeight()); + mUseCameraHintToast.show(); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_scan_qr_code: + if (hasPendingKeyFetches()) { + Toast.makeText(this, R.string.please_wait_for_keys_to_be_fetched, Toast.LENGTH_SHORT).show(); + } else { + new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE")); + return true; + } + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onStop() { + super.onStop(); + if (mUseCameraHintToast != null) { + mUseCameraHintToast.cancel(); + } + } + + @Override + protected void processFingerprintVerification(XmppUri uri) { + if (mConversation != null + && mAccount != null + && uri.hasFingerprints() + && mAccount.getAxolotlService().getCryptoTargets(mConversation).contains(uri.getJid())) { + boolean performedVerification = xmppConnectionService.verifyFingerprints(mAccount.getRoster().getContact(uri.getJid()),uri.getFingerprints()); + boolean keys = reloadFingerprints(); + if (performedVerification && !keys && !hasNoOtherTrustedKeys() && !hasPendingKeyFetches()) { + Toast.makeText(this,R.string.all_omemo_keys_have_been_verified, Toast.LENGTH_SHORT).show(); + finishOk(); + return; + } else if (performedVerification) { + Toast.makeText(this,R.string.verified_fingerprints,Toast.LENGTH_SHORT).show(); + } + } else { + reloadFingerprints(); + Log.d(Config.LOGTAG,"xmpp uri was: "+uri.getJid()+" has Fingerprints: "+Boolean.toString(uri.hasFingerprints())); + Toast.makeText(this,R.string.barcode_does_not_contain_fingerprints_for_this_conversation,Toast.LENGTH_SHORT).show(); + } + populateView(); + } + private void populateView() { setTitle(getString(R.string.trust_omemo_fingerprints)); ownKeys.removeAllViews(); @@ -108,16 +176,14 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate for(final String fingerprint : ownKeysToTrust.keySet()) { hasOwnKeys = true; addFingerprintRowWithListeners(ownKeys, mAccount, fingerprint, false, - XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)), false, + FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)), false, 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 + } ); } @@ -133,16 +199,14 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate final Map<String, Boolean> fingerprints = entry.getValue(); for (final String fingerprint : fingerprints.keySet()) { addFingerprintRowWithListeners(keysContainer, mAccount, fingerprint, false, - XmppAxolotlSession.Trust.fromBoolean(fingerprints.get(fingerprint)), false, + FingerprintStatus.createActive(fingerprints.get(fingerprint)), false, false, new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { fingerprints.put(fingerprint, isChecked); lockOrUnlockAsNeeded(); } - }, - null, - null + } ); } if (fingerprints.size() == 0) { @@ -184,7 +248,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate List<Jid> acceptedTargets = mConversation == null ? new ArrayList<Jid>() : mConversation.getAcceptedCryptoTargets(); ownKeysToTrust.clear(); AxolotlService service = this.mAccount.getAxolotlService(); - Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED); + Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided()); for(final IdentityKey identityKey : ownKeysSet) { if(!ownKeysToTrust.containsKey(identityKey)) { ownKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false); @@ -193,9 +257,9 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate synchronized (this.foreignKeysToTrust) { foreignKeysToTrust.clear(); for (Jid jid : contactJids) { - Set<IdentityKey> foreignKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, jid); + Set<IdentityKey> foreignKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), jid); if (hasNoOtherTrustedKeys(jid) && ownKeysSet.size() == 0) { - foreignKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, jid)); + foreignKeysSet.addAll(service.getKeysWithTrust(FingerprintStatus.createActive(false), jid)); } Map<String, Boolean> foreignFingerprints = new HashMap<>(); for (final IdentityKey identityKey : foreignKeysSet) { @@ -211,15 +275,19 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate return ownKeysSet.size() + foreignKeysToTrust.size() > 0; } - @Override public void onBackendConnected() { Intent intent = getIntent(); this.mAccount = extractAccount(intent); if (this.mAccount != null && intent != null) { String uuid = intent.getStringExtra("conversation"); this.mConversation = xmppConnectionService.findConversationByUuid(uuid); - reloadFingerprints(); - populateView(); + if (this.mPendingFingerprintVerificationUri != null) { + processFingerprintVerification(this.mPendingFingerprintVerificationUri); + this.mPendingFingerprintVerificationUri = null; + } else { + reloadFingerprints(); + populateView(); + } } } @@ -238,24 +306,32 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate @Override public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) { + final boolean keysToTrust = reloadFingerprints(); if (report != null) { lastFetchReport = report; runOnUiThread(new Runnable() { @Override public void run() { + if (mUseCameraHintToast != null && !keysToTrust) { + mUseCameraHintToast.cancel(); + } switch (report) { case ERROR: Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show(); break; + case SUCCESS_TRUSTED: + Toast.makeText(TrustKeysActivity.this,R.string.blindly_trusted_omemo_keys,Toast.LENGTH_LONG).show(); + break; case SUCCESS_VERIFIED: - Toast.makeText(TrustKeysActivity.this,R.string.verified_omemo_key_with_certificate,Toast.LENGTH_LONG).show(); + Toast.makeText(TrustKeysActivity.this, + Config.X509_VERIFICATION ? R.string.verified_omemo_key_with_certificate : R.string.all_omemo_keys_have_been_verified, + Toast.LENGTH_LONG).show(); break; } } }); } - boolean keysToTrust = reloadFingerprints(); if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { refreshUi(); } else { @@ -280,7 +356,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate for(final String fingerprint :ownKeysToTrust.keySet()) { mAccount.getAxolotlService().setFingerprintTrust( fingerprint, - XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint))); + FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint))); } List<Jid> acceptedTargets = mConversation == null ? new ArrayList<Jid>() : mConversation.getAcceptedCryptoTargets(); synchronized (this.foreignKeysToTrust) { @@ -293,7 +369,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate for (final String fingerprint : value.keySet()) { mAccount.getAxolotlService().setFingerprintTrust( fingerprint, - XmppAxolotlSession.Trust.fromBoolean(value.get(fingerprint))); + FingerprintStatus.createActive(value.get(fingerprint))); } } } diff --git a/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java b/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java index d8d02c12..c065bf9f 100644 --- a/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/VerifyOTRActivity.java @@ -173,11 +173,10 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer protected boolean verifyWithUri(XmppUri uri) { Contact contact = mConversation.getContact(); - if (this.mConversation.getContact().getJid().equals(uri.getJid()) && uri.getFingerprint() != null) { - contact.addOtrFingerprint(uri.getFingerprint()); + if (this.mConversation.getContact().getJid().equals(uri.getJid()) && uri.hasFingerprints()) { + xmppConnectionService.verifyFingerprints(contact,uri.getFingerprints()); Toast.makeText(this,R.string.verified,Toast.LENGTH_SHORT).show(); updateView(); - xmppConnectionService.syncRosterToDisk(contact.getAccount()); return true; } else { Toast.makeText(this,R.string.could_not_verify_fingerprint,Toast.LENGTH_SHORT).show(); diff --git a/src/main/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/main/java/eu/siacs/conversations/ui/WelcomeActivity.java index 59d58db5..2c9fc131 100644 --- a/src/main/java/eu/siacs/conversations/ui/WelcomeActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/WelcomeActivity.java @@ -8,22 +8,35 @@ import android.os.Bundle; import android.view.View; import android.widget.Button; +import java.util.List; + import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Account; + +public class WelcomeActivity extends XmppActivity { + + @Override + protected void refreshUiReal() { + + } + + @Override + void onBackendConnected() { -public class WelcomeActivity extends Activity { + } @Override protected void onCreate(final Bundle savedInstanceState) { if (getResources().getBoolean(R.bool.portrait_only)) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } + super.onCreate(savedInstanceState); + setContentView(R.layout.welcome); final ActionBar ab = getActionBar(); if (ab != null) { ab.setDisplayShowHomeEnabled(false); ab.setDisplayHomeAsUpEnabled(false); } - super.onCreate(savedInstanceState); - setContentView(R.layout.welcome); final Button createAccount = (Button) findViewById(R.id.create_account); createAccount.setOnClickListener(new View.OnClickListener() { @Override @@ -37,7 +50,15 @@ public class WelcomeActivity extends Activity { useOwnProvider.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - startActivity(new Intent(WelcomeActivity.this, EditAccountActivity.class)); + List<Account> accounts = xmppConnectionService.getAccounts(); + Intent intent = new Intent(WelcomeActivity.this, EditAccountActivity.class); + if (accounts.size() == 1) { + intent.putExtra("jid",accounts.get(0).getJid().toBareJid().toString()); + intent.putExtra("init",true); + } else if (accounts.size() >= 1) { + intent = new Intent(WelcomeActivity.this, ManageAccountActivity.class); + } + startActivity(intent); } }); diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index e8047ce2..583fab78 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -28,6 +28,7 @@ import android.graphics.Color; import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.net.ConnectivityManager; import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefRecord; @@ -43,24 +44,18 @@ import android.os.SystemClock; import android.preference.PreferenceManager; import android.text.InputType; import android.util.DisplayMetrics; -import android.util.Log; import android.util.Pair; 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; import com.google.zxing.EncodeHintType; -import com.google.zxing.WriterException; +import com.google.zxing.aztec.AztecWriter; import com.google.zxing.common.BitMatrix; -import com.google.zxing.qrcode.QRCodeWriter; -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import net.java.otr4j.session.SessionID; @@ -68,7 +63,6 @@ import java.io.FileNotFoundException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; @@ -77,19 +71,16 @@ import java.util.concurrent.atomic.AtomicInteger; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; -import eu.siacs.conversations.entities.Presence; import eu.siacs.conversations.entities.Presences; -import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.services.AvatarService; +import eu.siacs.conversations.services.BarcodeProvider; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder; -import eu.siacs.conversations.ui.widget.Switch; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.UIHelper; @@ -446,6 +437,16 @@ public abstract class XmppActivity extends Activity { } } + protected boolean isAffectedByDataSaver() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + return cm.isActiveNetworkMetered() + && cm.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; + } else { + return false; + } + } + protected boolean usingEnterKey() { return getPreferences().getBoolean("display_enter_key", false); } @@ -574,7 +575,7 @@ public abstract class XmppActivity extends Activity { xmppConnectionService.sendPresence(account); if (conversation != null) { conversation.setNextEncryption(Message.ENCRYPTION_PGP); - xmppConnectionService.databaseBackend.updateConversation(conversation); + xmppConnectionService.updateConversation(conversation); refreshUi(); } if (onSuccess != null) { @@ -584,7 +585,14 @@ public abstract class XmppActivity extends Activity { @Override public void error(int error, Account account) { - displayErrorDialog(error); + if (error == 0 && account != null) { + account.setPgpSignId(0); + account.unsetPgpSignature(); + xmppConnectionService.databaseBackend.updateAccount(account); + choosePgpSignId(account); + } else { + displayErrorDialog(error); + } } }); } @@ -761,164 +769,6 @@ 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; - } - }; - boolean active = true; - view.setOnLongClickListener(purge); - key.setOnLongClickListener(purge); - keyType.setOnLongClickListener(purge); - boolean x509 = Config.X509_VERIFICATION - && (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(!Config.X509_VERIFICATION || trust != XmppAxolotlSession.Trust.TRUSTED_X509); - if (Config.X509_VERIFICATION && 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()); - active = false; - break; - case INACTIVE_TRUSTED: - case INACTIVE_TRUSTED_X509: - trustToggle.setOnClickListener(null); - trustToggle.setChecked(true, false); - trustToggle.setEnabled(false); - key.setTextColor(getTertiaryTextColor()); - keyType.setTextColor(getTertiaryTextColor()); - active = false; - 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))); - - final View.OnClickListener toast; - if (!active) { - toast = new View.OnClickListener() { - @Override - public void onClick(View v) { - replaceToast(getString(R.string.this_device_is_no_longer_in_use), false); - } - }; - trustToggle.setOnClickListener(toast); - } else { - toast = new View.OnClickListener() { - @Override - public void onClick(View v) { - hideToast(); - } - }; - } - view.setOnClickListener(toast); - key.setOnClickListener(toast); - keyType.setOnClickListener(toast); - - 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.purge_key), - 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) { @@ -1142,7 +992,7 @@ public abstract class XmppActivity extends Activity { } protected boolean manuallyChangePresence() { - return getPreferences().getBoolean("manually_change_presence", false); + return getPreferences().getBoolean(SettingsActivity.MANUALLY_CHANGE_PRESENCE, false); } protected void unregisterNdefPushMessageCallback() { @@ -1209,7 +1059,7 @@ public abstract class XmppActivity extends Activity { Point size = new Point(); getWindowManager().getDefaultDisplay().getSize(size); final int width = (size.x < size.y ? size.x : size.y); - Bitmap bitmap = createQrCodeBitmap(uri, width); + Bitmap bitmap = BarcodeProvider.createAztecBitmap(uri, width); ImageView view = new ImageView(this); view.setBackgroundColor(Color.WHITE); view.setImageBitmap(bitmap); @@ -1219,31 +1069,6 @@ public abstract class XmppActivity extends Activity { } } - protected Bitmap createQrCodeBitmap(String input, int size) { - Log.d(Config.LOGTAG,"qr code requested size: "+size); - try { - final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter(); - final Hashtable<EncodeHintType, Object> hints = new Hashtable<>(); - hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); - final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints); - final int width = result.getWidth(); - final int height = result.getHeight(); - final int[] pixels = new int[width * height]; - for (int y = 0; y < height; y++) { - final int offset = y * width; - for (int x = 0; x < width; x++) { - pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT; - } - } - final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Log.d(Config.LOGTAG,"output size: "+width+"x"+height); - bitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return bitmap; - } catch (final WriterException e) { - return null; - } - } - protected Account extractAccount(Intent intent) { String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null; try { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 6a785594..69bc28e5 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -44,6 +44,7 @@ import java.util.regex.Pattern; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; @@ -54,11 +55,13 @@ import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.ui.ConversationActivity; import eu.siacs.conversations.ui.widget.ClickableMovementMethod; +import eu.siacs.conversations.ui.widget.CopyTextView; +import eu.siacs.conversations.ui.widget.ListSelectionManager; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.UIHelper; -public class MessageAdapter extends ArrayAdapter<Message> { +public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextView.CopyHandler { private static final int SENT = 0; private static final int RECEIVED = 1; @@ -76,17 +79,11 @@ public class MessageAdapter extends ArrayAdapter<Message> { private OnContactPictureClicked mOnContactPictureClickedListener; private OnContactPictureLongClicked mOnContactPictureLongClickedListener; - private OnLongClickListener openContextMenu = new OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - v.showContextMenu(); - return true; - } - }; private boolean mIndicateReceived = false; private boolean mUseGreenBackground = false; + private final ListSelectionManager listSelectionManager = new ListSelectionManager(); + public MessageAdapter(ConversationActivity activity, List<Message> messages) { super(activity, 0, messages); this.activity = activity; @@ -131,7 +128,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { } } - private void displayStatus(ViewHolder viewHolder, Message message, int type, boolean darkBackground) { + private void displayStatus(ViewHolder viewHolder, Message message, int type, boolean darkBackground, boolean inValidSession) { String filesize = null; String info = null; boolean error = false; @@ -207,12 +204,12 @@ public class MessageAdapter extends ArrayAdapter<Message> { 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() + FingerprintStatus status = message.getConversation() .getAccount().getAxolotlService().getFingerprintTrust( message.getFingerprint()); - if(trust == null || (!trust.trusted() && !trust.trustedInactive())) { - viewHolder.indicator.setColorFilter(activity.getWarningTextColor()); + if(status == null || (type == SENT ? !status.isTrusted() : (!status.isVerified() && inValidSession))) { + viewHolder.indicator.setColorFilter(0xffc64545); viewHolder.indicator.setAlpha(1.0f); } else { viewHolder.indicator.clearColorFilter(); @@ -308,32 +305,30 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.messageBody.setIncludeFontPadding(true); if (message.getBody() != null) { final String nick = UIHelper.getMessageDisplayName(message); - String body; - try { - body = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND, nick + " "); - } catch (ArrayIndexOutOfBoundsException e) { - body = message.getMergedBody(); + SpannableStringBuilder body = message.getMergedBody(); + boolean hasMeCommand = message.hasMeCommand(); + if (hasMeCommand) { + body = body.replace(0, Message.ME_COMMAND.length(), nick + " "); } if (body.length() > Config.MAX_DISPLAY_MESSAGE_CHARS) { - body = body.substring(0, Config.MAX_DISPLAY_MESSAGE_CHARS)+"\u2026"; + body = new SpannableStringBuilder(body, 0, Config.MAX_DISPLAY_MESSAGE_CHARS); + body.append("\u2026"); } - Spannable formattedBody = new SpannableString(body); - int i = body.indexOf(Message.MERGE_SEPARATOR); - while(i >= 0) { - final int end = i + Message.MERGE_SEPARATOR.length(); - formattedBody.setSpan(new RelativeSizeSpan(0.3f),i,end,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - i = body.indexOf(Message.MERGE_SEPARATOR,end); + Message.MergeSeparator[] mergeSeparators = body.getSpans(0, body.length(), Message.MergeSeparator.class); + for (Message.MergeSeparator mergeSeparator : mergeSeparators) { + int start = body.getSpanStart(mergeSeparator); + int end = body.getSpanEnd(mergeSeparator); + body.setSpan(new RelativeSizeSpan(0.3f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } if (message.getType() != Message.TYPE_PRIVATE) { - if (message.hasMeCommand()) { - formattedBody.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), + if (hasMeCommand) { + body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } else { String privateMarker; if (message.getStatus() <= Message.STATUS_RECEIVED) { - privateMarker = activity - .getString(R.string.private_message); + privateMarker = activity.getString(R.string.private_message); } else { final String to; if (message.getCounterpart() != null) { @@ -343,25 +338,26 @@ public class MessageAdapter extends ArrayAdapter<Message> { } privateMarker = activity.getString(R.string.private_message_to, to); } - formattedBody = new SpannableStringBuilder().append(privateMarker).append(' ').append(formattedBody); - formattedBody.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground,false)), 0, privateMarker - .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - formattedBody.setSpan(new StyleSpan(Typeface.BOLD), 0, - privateMarker.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - if (message.hasMeCommand()) { - formattedBody.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1, - privateMarker.length() + 1 + nick.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + body.insert(0, privateMarker); + int privateMarkerIndex = privateMarker.length(); + body.insert(privateMarkerIndex, " "); + body.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground, false)), + 0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + body.setSpan(new StyleSpan(Typeface.BOLD), + 0, privateMarkerIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + if (hasMeCommand) { + body.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarkerIndex + 1, + privateMarkerIndex + 1 + nick.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } - Linkify.addLinks(formattedBody, Linkify.WEB_URLS); - Linkify.addLinks(formattedBody, XMPP_PATTERN, "xmpp"); - Linkify.addLinks(formattedBody, GeoHelper.GEO_URI, "geo"); + Linkify.addLinks(body, Linkify.WEB_URLS); + Linkify.addLinks(body, XMPP_PATTERN, "xmpp"); + Linkify.addLinks(body, GeoHelper.GEO_URI, "geo"); viewHolder.messageBody.setAutoLinkMask(0); - viewHolder.messageBody.setText(formattedBody); + viewHolder.messageBody.setText(body); viewHolder.messageBody.setTextIsSelectable(true); viewHolder.messageBody.setMovementMethod(ClickableMovementMethod.getInstance()); + listSelectionManager.onUpdate(viewHolder.messageBody, message); } else { viewHolder.messageBody.setText(""); viewHolder.messageBody.setTextIsSelectable(false); @@ -370,7 +366,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.messageBody.setLinkTextColor(this.getMessageTextColor(darkBackground, true)); viewHolder.messageBody.setHighlightColor(activity.getResources().getColor(darkBackground ? (type == SENT || !mUseGreenBackground ? R.color.black26 : R.color.grey800) : R.color.grey500)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); - viewHolder.messageBody.setOnLongClickListener(openContextMenu); } private void displayDownloadableMessage(ViewHolder viewHolder, @@ -386,7 +381,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { activity.startDownloadable(message); } }); - viewHolder.download_button.setOnLongClickListener(openContextMenu); } private void displayOpenableMessage(ViewHolder viewHolder,final Message message) { @@ -401,7 +395,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { openDownloadable(message); } }); - viewHolder.download_button.setOnLongClickListener(openContextMenu); } private void displayLocationMessage(ViewHolder viewHolder, final Message message) { @@ -416,7 +409,6 @@ public class MessageAdapter extends ArrayAdapter<Message> { showLocation(message); } }); - viewHolder.download_button.setOnLongClickListener(openContextMenu); } private void displayImageMessage(ViewHolder viewHolder, @@ -454,12 +446,11 @@ public class MessageAdapter extends ArrayAdapter<Message> { openDownloadable(message); } }); - viewHolder.image.setOnLongClickListener(openContextMenu); } private void loadMoreMessages(Conversation conversation) { conversation.setLastClearHistory(0); - activity.xmppConnectionService.databaseBackend.updateConversation(conversation); + activity.xmppConnectionService.updateConversation(conversation); conversation.setHasMessagesLeftOnServer(true); conversation.setFirstMamReference(null); long timestamp = conversation.getLastMessageTransmitted(); @@ -474,7 +465,8 @@ public class MessageAdapter extends ArrayAdapter<Message> { @Override public View getView(int position, View view, ViewGroup parent) { final Message message = getItem(position); - final boolean isInValidSession = message.isValidInSession(); + final boolean omemoEncryption = message.getEncryption() == Message.ENCRYPTION_AXOLOTL; + final boolean isInValidSession = message.isValidInSession() && (!omemoEncryption || message.isTrusted()); final Conversation conversation = message.getConversation(); final Account account = conversation.getAccount(); final int type = getItemViewType(position); @@ -496,7 +488,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator); viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view + viewHolder.messageBody = (CopyTextView) view .findViewById(R.id.message_body); viewHolder.time = (TextView) view .findViewById(R.id.message_time); @@ -517,7 +509,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator); viewHolder.image = (ImageView) view .findViewById(R.id.message_image); - viewHolder.messageBody = (TextView) view + viewHolder.messageBody = (CopyTextView) view .findViewById(R.id.message_body); viewHolder.time = (TextView) view .findViewById(R.id.message_time); @@ -535,6 +527,10 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder = null; break; } + if (viewHolder.messageBody != null) { + listSelectionManager.onCreate(viewHolder.messageBody); + viewHolder.messageBody.setCopyHandler(this); + } view.setTag(viewHolder); } else { viewHolder = (ViewHolder) view.getTag(); @@ -676,15 +672,31 @@ public class MessageAdapter extends ArrayAdapter<Message> { } else { viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); viewHolder.encryption.setVisibility(View.VISIBLE); - viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption())); + if (omemoEncryption && !message.isTrusted()) { + viewHolder.encryption.setText(R.string.not_trusted); + } else { + viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption())); + } } } - displayStatus(viewHolder, message, type, darkBackground); + displayStatus(viewHolder, message, type, darkBackground, isInValidSession); return view; } + @Override + public void notifyDataSetChanged() { + listSelectionManager.onBeforeNotifyDataSetChanged(); + super.notifyDataSetChanged(); + listSelectionManager.onAfterNotifyDataSetChanged(); + } + + @Override + public String transformTextForCopy(CharSequence text, int start, int end) { + return text.toString().substring(start, end); + } + public void openDownloadable(Message message) { DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message); if (!file.exists()) { @@ -699,7 +711,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { Uri uri; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N || Config.ONLY_INTERNAL_STORAGE) { try { - uri = FileProvider.getUriForFile(activity, FileBackend.CONVERSATIONS_FILE_PROVIDER, file); + uri = FileBackend.getUriForFile(activity, file); } catch (IllegalArgumentException e) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Toast.makeText(activity, activity.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show(); @@ -741,6 +753,15 @@ public class MessageAdapter extends ArrayAdapter<Message> { this.mUseGreenBackground = activity.useGreenBackground(); } + public TextView getMessageBody(View view) { + final Object tag = view.getTag(); + if (tag instanceof ViewHolder) { + final ViewHolder viewHolder = (ViewHolder) tag; + return viewHolder.messageBody; + } + return null; + } + public interface OnContactPictureClicked { void onContactPictureClicked(Message message); } @@ -757,7 +778,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { protected ImageView indicator; protected ImageView indicatorReceived; protected TextView time; - protected TextView messageBody; + protected CopyTextView messageBody; protected ImageView contact_picture; protected TextView status_message; protected TextView encryption; diff --git a/src/main/java/eu/siacs/conversations/ui/widget/CopyTextView.java b/src/main/java/eu/siacs/conversations/ui/widget/CopyTextView.java new file mode 100644 index 00000000..bed56192 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/widget/CopyTextView.java @@ -0,0 +1,66 @@ +package eu.siacs.conversations.ui.widget; + +import android.annotation.TargetApi; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.TextView; + +public class CopyTextView extends TextView { + + public CopyTextView(Context context) { + super(context); + } + + public CopyTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CopyTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @SuppressWarnings("unused") + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public CopyTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public interface CopyHandler { + public String transformTextForCopy(CharSequence text, int start, int end); + } + + private CopyHandler copyHandler; + + public void setCopyHandler(CopyHandler copyHandler) { + this.copyHandler = copyHandler; + } + + @Override + public boolean onTextContextMenuItem(int id) { + CharSequence text = getText(); + int min = 0; + int max = text.length(); + if (isFocused()) { + final int selStart = getSelectionStart(); + final int selEnd = getSelectionEnd(); + min = Math.max(0, Math.min(selStart, selEnd)); + max = Math.max(0, Math.max(selStart, selEnd)); + } + String textForCopy = null; + if (id == android.R.id.copy && copyHandler != null) { + textForCopy = copyHandler.transformTextForCopy(getText(), min, max); + } + try { + return super.onTextContextMenuItem(id); + } finally { + if (textForCopy != null) { + ClipboardManager clipboard = (ClipboardManager) getContext(). + getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setPrimaryClip(ClipData.newPlainText(null, textForCopy)); + } + } + } +}
\ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/ui/widget/ListSelectionManager.java b/src/main/java/eu/siacs/conversations/ui/widget/ListSelectionManager.java new file mode 100644 index 00000000..9e256448 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/widget/ListSelectionManager.java @@ -0,0 +1,201 @@ +package eu.siacs.conversations.ui.widget; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.text.Selection; +import android.text.Spannable; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.TextView; + +public class ListSelectionManager { + + private static final int MESSAGE_SEND_RESET = 1; + private static final int MESSAGE_RESET = 2; + private static final int MESSAGE_START_SELECTION = 3; + + private static final Handler HANDLER = new Handler(Looper.getMainLooper(), new Handler.Callback() { + + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_SEND_RESET: { + // Skip one more message queue loop + HANDLER.obtainMessage(MESSAGE_RESET, msg.obj).sendToTarget(); + return true; + } + case MESSAGE_RESET: { + final ListSelectionManager listSelectionManager = (ListSelectionManager) msg.obj; + listSelectionManager.futureSelectionIdentifier = null; + return true; + } + case MESSAGE_START_SELECTION: { + final StartSelectionHolder holder = (StartSelectionHolder) msg.obj; + holder.listSelectionManager.futureSelectionIdentifier = null; + startSelection(holder.textView, holder.start, holder.end); + return true; + } + } + return false; + } + }); + + private static class StartSelectionHolder { + + public final ListSelectionManager listSelectionManager; + public final TextView textView; + public final int start; + public final int end; + + public StartSelectionHolder(ListSelectionManager listSelectionManager, TextView textView, + int start, int end) { + this.listSelectionManager = listSelectionManager; + this.textView = textView; + this.start = start; + this.end = end; + } + } + + private ActionMode selectionActionMode; + private Object selectionIdentifier; + private TextView selectionTextView; + + private Object futureSelectionIdentifier; + private int futureSelectionStart; + private int futureSelectionEnd; + + public void onCreate(TextView textView) { + final CustomCallback callback = new CustomCallback(textView); + textView.setCustomSelectionActionModeCallback(callback); + } + + public void onUpdate(TextView textView, Object identifier) { + if (SUPPORTED) { + CustomCallback callback = (CustomCallback) textView.getCustomSelectionActionModeCallback(); + callback.identifier = identifier; + if (futureSelectionIdentifier == identifier) { + HANDLER.obtainMessage(MESSAGE_START_SELECTION, new StartSelectionHolder(this, + textView, futureSelectionStart, futureSelectionEnd)).sendToTarget(); + } + } + } + + public void onBeforeNotifyDataSetChanged() { + if (SUPPORTED) { + HANDLER.removeMessages(MESSAGE_SEND_RESET); + HANDLER.removeMessages(MESSAGE_RESET); + HANDLER.removeMessages(MESSAGE_START_SELECTION); + if (selectionActionMode != null) { + final CharSequence text = selectionTextView.getText(); + futureSelectionIdentifier = selectionIdentifier; + futureSelectionStart = Selection.getSelectionStart(text); + futureSelectionEnd = Selection.getSelectionEnd(text); + selectionActionMode.finish(); + selectionActionMode = null; + selectionIdentifier = null; + selectionTextView = null; + } + } + } + + public void onAfterNotifyDataSetChanged() { + if (SUPPORTED && futureSelectionIdentifier != null) { + HANDLER.obtainMessage(MESSAGE_SEND_RESET, this).sendToTarget(); + } + } + + private class CustomCallback implements ActionMode.Callback { + + private final TextView textView; + public Object identifier; + + public CustomCallback(TextView textView) { + this.textView = textView; + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + selectionActionMode = mode; + selectionIdentifier = identifier; + selectionTextView = textView; + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + if (selectionActionMode == mode) { + selectionActionMode = null; + selectionIdentifier = null; + selectionTextView = null; + } + } + } + + private static final Field FIELD_EDITOR; + private static final Method METHOD_START_SELECTION; + private static final boolean SUPPORTED; + + static { + Field editor; + try { + editor = TextView.class.getDeclaredField("mEditor"); + editor.setAccessible(true); + } catch (Exception e) { + editor = null; + } + FIELD_EDITOR = editor; + Method startSelection = null; + if (editor != null) { + String[] startSelectionNames = {"startSelectionActionMode", "startSelectionActionModeWithSelection"}; + for (String startSelectionName : startSelectionNames) { + try { + startSelection = editor.getType().getDeclaredMethod(startSelectionName); + startSelection.setAccessible(true); + break; + } catch (Exception e) { + startSelection = null; + } + } + } + METHOD_START_SELECTION = startSelection; + SUPPORTED = FIELD_EDITOR != null && METHOD_START_SELECTION != null; + } + + public static boolean isSupported() { + return SUPPORTED; + } + + public static void startSelection(TextView textView) { + startSelection(textView, 0, textView.getText().length()); + } + + public static void startSelection(TextView textView, int start, int end) { + final CharSequence text = textView.getText(); + if (SUPPORTED && start >= 0 && end > start && textView.isTextSelectable() && text instanceof Spannable) { + final Spannable spannable = (Spannable) text; + start = Math.min(start, spannable.length()); + end = Math.min(end, spannable.length()); + Selection.setSelection(spannable, start, end); + try { + final Object editor = FIELD_EDITOR != null ? FIELD_EDITOR.get(textView) : textView; + METHOD_START_SELECTION.invoke(editor); + } catch (Exception e) { + } + } + } +}
\ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java index 38ebced1..f1a9d8c4 100644 --- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java @@ -206,9 +206,13 @@ public final class CryptoHelper { } public static String getAccountFingerprint(Account account) { + return getFingerprint(account.getJid().toBareJid().toString()); + } + + public static String getFingerprint(String value) { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); - return bytesToHex(md.digest(account.getJid().toBareJid().toString().getBytes("UTF-8"))); + return bytesToHex(md.digest(value.getBytes("UTF-8"))); } catch (Exception e) { return ""; } diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java index ac64cf2e..a8aba6db 100644 --- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java @@ -19,6 +19,7 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Random; import java.util.TreeMap; import java.util.Map; @@ -148,26 +149,29 @@ public class DNSHelper { for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) { for (Record rr : rrset) { Data d = rr.getPayload(); - if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) { + final String name = rr.getName() != null ? rr.getName().toLowerCase(Locale.US) : null; + if (d instanceof SRV && NameUtil.idnEquals(qname, name)) { SRV srv = (SRV) d; if (!priorities.containsKey(srv.getPriority())) { priorities.put(srv.getPriority(),new ArrayList<TlsSrv>()); } priorities.get(srv.getPriority()).add(new TlsSrv(srv, tls)); + } else if (d instanceof SRV) { + Log.d(Config.LOGTAG,"found unrecognized SRV record with name: "+name); } if (d instanceof A) { A a = (A) d; - if (!ips4.containsKey(rr.getName())) { - ips4.put(rr.getName(), new ArrayList<String>()); + if (!ips4.containsKey(name)) { + ips4.put(name, new ArrayList<String>()); } - ips4.get(rr.getName()).add(a.toString()); + ips4.get(name).add(a.toString()); } if (d instanceof AAAA) { AAAA aaaa = (AAAA) d; - if (!ips6.containsKey(rr.getName())) { - ips6.put(rr.getName(), new ArrayList<String>()); + if (!ips6.containsKey(name)) { + ips6.put(name, new ArrayList<String>()); } - ips6.get(rr.getName()).add("[" + aaaa.toString() + "]"); + ips6.get(name).add("[" + aaaa.toString() + "]"); } } } @@ -177,8 +181,8 @@ public class DNSHelper { Bundle bundle = new Bundle(); try { client.setTimeout(Config.SOCKET_TIMEOUT * 1000); - final String qname = "_xmpp-client._tcp." + host; - final String tlsQname = "_xmpps-client._tcp." + host; + final String qname = "_xmpp-client._tcp." + host.toLowerCase(Locale.US); + final String tlsQname = "_xmpps-client._tcp." + host.toLowerCase(Locale.US); Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host); final Map<Integer, List<TlsSrv>> priorities = new TreeMap<>(); @@ -218,27 +222,28 @@ public class DNSHelper { } for (final TlsSrv tlsSrv : result) { final SRV srv = tlsSrv.srv; - if (ips6.containsKey(srv.getName())) { - values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6, tlsSrv.tls)); + final String name = srv.getName() != null ? srv.getName().toLowerCase(Locale.US) : null; + if (ips6.containsKey(name)) { + values.add(createNamePortBundle(name,srv.getPort(),ips6, tlsSrv.tls)); } else { try { - DNSMessage response = client.query(srv.getName(), TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress()); + DNSMessage response = client.query(name, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress()); for (int i = 0; i < response.getAnswers().length; ++i) { - values.add(createNamePortBundle(srv.getName(), srv.getPort(), response.getAnswers()[i].getPayload(), tlsSrv.tls)); + values.add(createNamePortBundle(name, srv.getPort(), response.getAnswers()[i].getPayload(), tlsSrv.tls)); } } catch (SocketTimeoutException e) { Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress()); } } - if (ips4.containsKey(srv.getName())) { - values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4, tlsSrv.tls)); + if (ips4.containsKey(name)) { + values.add(createNamePortBundle(name,srv.getPort(),ips4, tlsSrv.tls)); } else { - DNSMessage response = client.query(srv.getName(), TYPE.A, CLASS.IN, dnsServer.getHostAddress()); + DNSMessage response = client.query(name, TYPE.A, CLASS.IN, dnsServer.getHostAddress()); for(int i = 0; i < response.getAnswers().length; ++i) { - values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload(), tlsSrv.tls)); + values.add(createNamePortBundle(name,srv.getPort(),response.getAnswers()[i].getPayload(), tlsSrv.tls)); } } - values.add(createNamePortBundle(srv.getName(), srv.getPort(), tlsSrv.tls)); + values.add(createNamePortBundle(name, srv.getPort(), tlsSrv.tls)); } bundle.putParcelableArrayList("values", values); } catch (SocketTimeoutException e) { diff --git a/src/main/java/eu/siacs/conversations/utils/FileWriterException.java b/src/main/java/eu/siacs/conversations/utils/FileWriterException.java new file mode 100644 index 00000000..f406f419 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/FileWriterException.java @@ -0,0 +1,4 @@ +package eu.siacs.conversations.utils; + +public class FileWriterException extends Exception { +} diff --git a/src/main/java/eu/siacs/conversations/utils/TLSSocketFactory.java b/src/main/java/eu/siacs/conversations/utils/TLSSocketFactory.java new file mode 100644 index 00000000..9d8a4111 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/TLSSocketFactory.java @@ -0,0 +1,69 @@ +package eu.siacs.conversations.utils; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; + +public class TLSSocketFactory extends SSLSocketFactory { + + private final SSLSocketFactory internalSSLSocketFactory; + + public TLSSocketFactory(X509TrustManager[] trustManager, SecureRandom random) throws KeyManagementException, NoSuchAlgorithmException { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, trustManager, random); + this.internalSSLSocketFactory = context.getSocketFactory(); + } + + @Override + public String[] getDefaultCipherSuites() { + return CryptoHelper.getOrderedCipherSuites(internalSSLSocketFactory.getDefaultCipherSuites()); + } + + @Override + public String[] getSupportedCipherSuites() { + return internalSSLSocketFactory.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort)); + } + + private static Socket enableTLSOnSocket(Socket socket) { + if(socket != null && (socket instanceof SSLSocket)) { + try { + SSLSocketHelper.setSecurity((SSLSocket) socket); + } catch (NoSuchAlgorithmException e) { + //ignoring + } + } + return socket; + } +}
\ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/utils/Xmlns.java b/src/main/java/eu/siacs/conversations/utils/Xmlns.java index de0a29ce..3e725059 100644 --- a/src/main/java/eu/siacs/conversations/utils/Xmlns.java +++ b/src/main/java/eu/siacs/conversations/utils/Xmlns.java @@ -6,4 +6,5 @@ public final class Xmlns { 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 STANZA_IDS = "urn:xmpp:sid:0"; } diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java index 15a6c9a1..e16377cf 100644 --- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java +++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java @@ -4,7 +4,9 @@ import android.net.Uri; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import java.util.ArrayList; import java.util.List; +import java.util.Locale; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -13,7 +15,11 @@ public class XmppUri { protected String jid; protected boolean muc; - protected String fingerprint; + protected List<Fingerprint> fingerprints = new ArrayList<>(); + private String body; + + public static final String OMEMO_URI_PARAM = "omemo-sid-"; + public static final String OTR_URI_PARAM = "otr-fingerprint"; public XmppUri(String uri) { try { @@ -50,13 +56,14 @@ public class XmppUri { muc = segments.size() > 1 && "j".equalsIgnoreCase(segments.get(0)); } else if ("xmpp".equalsIgnoreCase(scheme)) { // sample: xmpp:foo@bar.com - muc = "join".equalsIgnoreCase(uri.getQuery()); + muc = isMuc(uri.getQuery()); if (uri.getAuthority() != null) { jid = uri.getAuthority(); } else { jid = uri.getSchemeSpecificPart().split("\\?")[0]; } - fingerprint = parseFingerprint(uri.getQuery()); + this.fingerprints = parseFingerprints(uri.getQuery()); + this.body = parseBody(uri.getQuery()); } else if ("imto".equalsIgnoreCase(scheme)) { // sample: imto://xmpp/foo@bar.com try { @@ -73,18 +80,52 @@ public class XmppUri { } } - protected String parseFingerprint(String query) { - if (query == null) { - return null; - } else { - final String NEEDLE = "otr-fingerprint="; - int index = query.indexOf(NEEDLE); - if (index >= 0 && query.length() >= (NEEDLE.length() + index + 40)) { - return query.substring(index + NEEDLE.length(), index + NEEDLE.length() + 40); - } else { - return null; + protected List<Fingerprint> parseFingerprints(String query) { + List<Fingerprint> fingerprints = new ArrayList<>(); + String[] pairs = query == null ? new String[0] : query.split(";"); + for(String pair : pairs) { + String[] parts = pair.split("=",2); + if (parts.length == 2) { + String key = parts[0].toLowerCase(Locale.US); + String value = parts[1].toLowerCase(Locale.US); + if (OTR_URI_PARAM.equals(key)) { + fingerprints.add(new Fingerprint(FingerprintType.OTR,value)); + } + if (key.startsWith(OMEMO_URI_PARAM)) { + try { + int id = Integer.parseInt(key.substring(OMEMO_URI_PARAM.length())); + fingerprints.add(new Fingerprint(FingerprintType.OMEMO,value,id)); + } catch (Exception e) { + //ignoring invalid device id + } + } } } + return fingerprints; + } + + protected String parseBody(String query) { + for(String pair : query == null ? new String[0] : query.split(";")) { + final String[] parts = pair.split("=",2); + if (parts.length == 2 && "body".equals(parts[0].toLowerCase(Locale.US))) { + try { + return URLDecoder.decode(parts[1],"UTF-8"); + } catch (UnsupportedEncodingException e) { + return null; + } + } + } + return null; + } + + protected boolean isMuc(String query) { + for(String pair : query == null ? new String[0] : query.split(";")) { + final String[] parts = pair.split("=",2); + if (parts.length == 1 && "join".equals(parts[0])) { + return true; + } + } + return false; } public Jid getJid() { @@ -95,7 +136,40 @@ public class XmppUri { } } - public String getFingerprint() { - return this.fingerprint; + public String getBody() { + return body; + } + + public List<Fingerprint> getFingerprints() { + return this.fingerprints; + } + + public boolean hasFingerprints() { + return fingerprints.size() > 0; + } + public enum FingerprintType { + OMEMO, + OTR + } + + public static class Fingerprint { + public final FingerprintType type; + public final String fingerprint; + public final int deviceId; + + public Fingerprint(FingerprintType type, String fingerprint) { + this(type, fingerprint, 0); + } + + public Fingerprint(FingerprintType type, String fingerprint, int deviceId) { + this.type = type; + this.fingerprint = fingerprint; + this.deviceId = deviceId; + } + + @Override + public String toString() { + return type.toString()+": "+fingerprint+(deviceId != 0 ? " "+String.valueOf(deviceId) : ""); + } } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 383e990d..128e1187 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -218,7 +218,10 @@ public class XmppConnection implements Runnable { mXmppConnectionService = service; } - protected void changeStatus(final Account.State nextStatus) { + protected synchronized void changeStatus(final Account.State nextStatus) { + if (Thread.currentThread().isInterrupted()) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": not changing status to "+nextStatus+" because thread was interrupted"); + } if (account.getStatus() != nextStatus) { if ((nextStatus == Account.State.OFFLINE) && (account.getStatus() != Account.State.CONNECTING) @@ -262,6 +265,7 @@ public class XmppConnection implements Runnable { break; } try { + Socket localSocket; shouldAuthenticate = needsBinding = !account.isOptionSet(Account.OPTION_REGISTER); tagReader = new XmlReader(wakeLock); tagWriter = new TagWriter(); @@ -276,8 +280,15 @@ public class XmppConnection implements Runnable { destination = account.getHostname(); } Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via Tor"); - socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort()); - startXmpp(); + localSocket = SocksSocketFactory.createSocketOverTor(destination, account.getPort()); + try { + startXmpp(localSocket); + } catch (InterruptedException e) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream"); + return; + } catch (Exception e) { + throw new IOException(e.getMessage()); + } } else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) { InetSocketAddress address = new InetSocketAddress(account.getHostname(), account.getPort()); @@ -288,33 +299,47 @@ public class XmppConnection implements Runnable { if (features.encryptionEnabled) { try { final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); - socket = tlsFactoryVerifier.factory.createSocket(); - socket.connect(address, Config.SOCKET_TIMEOUT * 1000); - final SSLSession session = ((SSLSocket) socket).getSession(); + localSocket = tlsFactoryVerifier.factory.createSocket(); + localSocket.connect(address, Config.SOCKET_TIMEOUT * 1000); + final SSLSession session = ((SSLSocket) localSocket).getSession(); if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), session)) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); throw new SecurityException(); } } catch (KeyManagementException e) { features.encryptionEnabled = false; - socket = new Socket(); + localSocket = new Socket(); } } else { - socket = new Socket(); - socket.connect(address, Config.SOCKET_TIMEOUT * 1000); + localSocket = new Socket(); + localSocket.connect(address, Config.SOCKET_TIMEOUT * 1000); } } catch (IOException e) { throw new UnknownHostException(); } - startXmpp(); + try { + startXmpp(localSocket); + } catch (InterruptedException e) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream"); + return; + } catch (Exception e) { + throw new IOException(e.getMessage()); + } } else if (DNSHelper.isIp(account.getServer().toString())) { - socket = new Socket(); + localSocket = new Socket(); try { - socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000); + localSocket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000); } catch (IOException e) { throw new UnknownHostException(); } - startXmpp(); + try { + startXmpp(localSocket); + } catch (InterruptedException e) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream"); + return; + } catch (Exception e) { + throw new IOException(e.getMessage()); + } } else { final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService); final ArrayList<Parcelable> values = result.getParcelableArrayList("values"); @@ -350,32 +375,37 @@ public class XmppConnection implements Runnable { } if (!features.encryptionEnabled) { - socket = new Socket(); - socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); + localSocket = new Socket(); + localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000); } else { final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); - socket = tlsFactoryVerifier.factory.createSocket(); + localSocket = tlsFactoryVerifier.factory.createSocket(); - if (socket == null) { + if (localSocket == 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"); + SSLSocketHelper.setSecurity((SSLSocket) localSocket); + SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) localSocket, account.getServer().getDomainpart()); + SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) localSocket, "xmpp-client"); - socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); + localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000); - if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) { + if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) localSocket).getSession())) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); throw new SecurityException(); } } - - if (startXmpp()) + if (startXmpp(localSocket)) { break; // successfully connected to server that speaks xmpp + } else { + localSocket.close(); + } } catch (final SecurityException e) { throw e; + } catch (InterruptedException e) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream"); + return; } catch (final Throwable e) { Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() + "(" + e.getClass().getName() + ")"); if (!iterator.hasNext()) { @@ -385,8 +415,10 @@ public class XmppConnection implements Runnable { } } processStream(); - } catch (final java.lang.SecurityException e) { + } catch (final java.lang.SecurityException e) { this.changeStatus(Account.State.MISSING_INTERNET_PERMISSION); + } catch (final RegistrationNotSupportedException e) { + this.changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED); } catch (final IncompatibleServerException e) { this.changeStatus(Account.State.INCOMPATIBLE_SERVER); } catch (final SecurityException e) { @@ -410,12 +442,16 @@ public class XmppConnection implements Runnable { this.changeStatus(Account.State.OFFLINE); this.attempt = Math.max(0, this.attempt - 1); } finally { - forceCloseSocket(); - if (wakeLock.isHeld()) { - try { - wakeLock.release(); - } catch (final RuntimeException ignored) { + if (!Thread.currentThread().isInterrupted()) { + forceCloseSocket(); + if (wakeLock.isHeld()) { + try { + wakeLock.release(); + } catch (final RuntimeException ignored) { + } } + } else { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": not force closing socket and releasing wake lock because thread was interrupted"); } } } @@ -423,27 +459,18 @@ public class XmppConnection implements Runnable { /** * 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 { + private boolean startXmpp(Socket socket) throws Exception { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + this.socket = socket; 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; + final Tag tag = tagReader.readTag(); + return tag != null && tag.isStart("stream"); } private static class TlsFactoryVerifier { @@ -812,10 +839,8 @@ public class XmppConnection implements Runnable { } else { throw new IncompatibleServerException(); } - } else if (!this.streamFeatures.hasChild("register") - && account.isOptionSet(Account.OPTION_REGISTER)) { - forceCloseSocket(); - changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED); + } else if (!this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) { + throw new RegistrationNotSupportedException(); } else if (this.streamFeatures.hasChild("mechanisms") && shouldAuthenticate && (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS)) { @@ -950,7 +975,7 @@ public class XmppConnection implements Runnable { } public void resetEverything() { - resetAttemptCount(); + resetAttemptCount(true); resetStreamId(); clearIqCallbacks(); mStanzaQueue.clear(); @@ -1359,30 +1384,6 @@ public class XmppConnection implements Runnable { } } - public void waitForPush() { - if (tagWriter.isActive()) { - tagWriter.finish(); - new Thread(new Runnable() { - @Override - public void run() { - try { - while(!tagWriter.finished()) { - Thread.sleep(10); - } - socket.close(); - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream"); - changeStatus(Account.State.OFFLINE); - } catch (IOException | InterruptedException e) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": error while closing socket for waitForPush()"); - } - } - }).start(); - } else { - forceCloseSocket(); - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)"); - } - } - private void forceCloseSocket() { if (socket != null) { try { @@ -1526,9 +1527,11 @@ public class XmppConnection implements Runnable { this.sendPacket(new InactivePacket()); } - public void resetAttemptCount() { + public void resetAttemptCount(boolean resetConnectTime) { this.attempt = 0; - this.lastConnect = 0; + if (resetConnectTime) { + this.lastConnect = 0; + } } public void setInteractive(boolean interactive) { @@ -1567,6 +1570,10 @@ public class XmppConnection implements Runnable { } + private class RegistrationNotSupportedException extends IOException { + + } + public enum Identity { FACEBOOK, SLACK, @@ -1685,6 +1692,10 @@ public class XmppConnection implements Runnable { return -1; } } + + public boolean stanzaIds() { + return hasDiscoFeature(account.getJid().toBareJid(),Xmlns.STANZA_IDS); + } } private IqGenerator getIqGenerator() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java index 6430d41e..f551dd20 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java @@ -21,10 +21,28 @@ public final class Jid { private final String domainpart; private final String resourcepart; + private static final char[] JID_ESCAPING_CHARS = {' ','"','&','\'','/',':','<','>','@','\\'}; + + // It's much more efficient to store the ful JID as well as the parts instead of figuring them + // all out every time (since some characters are displayed but aren't used for comparisons). + private final String displayjid; + public String getLocalpart() { return localpart; } + public String getUnescapedLocalpart() { + if (localpart == null || !localpart.contains("\\")) { + return localpart; + } else { + String localpart = this.localpart; + for(char c : JID_ESCAPING_CHARS) { + localpart = localpart.replace(String.format ("\\%02x", (int)c),String.valueOf(c)); + } + return localpart; + } + } + public String getDomainpart() { return IDN.toUnicode(domainpart); } @@ -69,6 +87,7 @@ public final class Jid { Jid fromCache = Jid.cache.get(jid); if (fromCache != null) { + displayjid = fromCache.displayjid; localpart = fromCache.localpart; domainpart = fromCache.domainpart; resourcepart = fromCache.resourcepart; @@ -89,6 +108,8 @@ public final class Jid { throw new InvalidJidException(InvalidJidException.INVALID_CHARACTER); } + String finaljid; + final int domainpartStart; final int atLoc = jid.indexOf("@"); final int slashLoc = jid.indexOf("/"); @@ -96,6 +117,7 @@ public final class Jid { // or there are one or more "@" signs but they're all in the resourcepart (eg. "example.net/@/rp@"): if (atCount == 0 || (atCount > 0 && slashLoc != -1 && atLoc > slashLoc)) { localpart = ""; + finaljid = ""; domainpartStart = 0; } else { final String lp = jid.substring(0, atLoc); @@ -108,6 +130,7 @@ public final class Jid { throw new InvalidJidException(InvalidJidException.INVALID_PART_LENGTH); } domainpartStart = atLoc + 1; + finaljid = lp + "@"; } final String dp; @@ -126,6 +149,7 @@ public final class Jid { } catch (final StringprepException e) { throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); } + finaljid = finaljid + dp + "/" + rp; } else { resourcepart = ""; try{ @@ -133,6 +157,7 @@ public final class Jid { } catch (final StringprepException e) { throw new InvalidJidException(InvalidJidException.STRINGPREP_FAIL, e); } + finaljid = finaljid + dp; } // Remove trailing "." before storing the domain part. @@ -156,6 +181,8 @@ public final class Jid { } Jid.cache.put(jid, this); + + this.displayjid = finaljid; } public Jid toBareJid() { @@ -178,6 +205,10 @@ public final class Jid { @Override public String toString() { + return displayjid; + } + + public String toPreppedString() { String out; if (hasLocalpart()) { out = localpart + '@' + domainpart; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index 3099b570..5461b9c6 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -25,6 +25,7 @@ import eu.siacs.conversations.entities.Presence; import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.TransferablePlaceholder; +import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; @@ -87,7 +88,7 @@ public class JingleConnection implements Transferable { @Override public void onIqPacketReceived(Account account, IqPacket packet) { if (packet.getType() != IqPacket.TYPE.RESULT) { - fail(); + fail(IqParser.extractErrorMessage(packet)); } } }; @@ -488,7 +489,7 @@ public class JingleConnection implements Transferable { mJingleStatus = JINGLE_STATUS_INITIATED; mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED); } else { - fail(); + fail(IqParser.extractErrorMessage(packet)); } } }); @@ -619,6 +620,10 @@ public class JingleConnection implements Transferable { if (cid != null) { Log.d(Config.LOGTAG, "candidate used by counterpart:" + cid); JingleCandidate candidate = getCandidate(cid); + if (candidate == null) { + Log.d(Config.LOGTAG,"could not find candidate with cid="+cid); + return false; + } candidate.flagAsUsedByCounterpart(); this.receivedCandidate = true; if ((mJingleStatus == JINGLE_STATUS_ACCEPTED) @@ -846,6 +851,10 @@ public class JingleConnection implements Transferable { } private void fail() { + fail(null); + } + + private void fail(String errorMessage) { this.mJingleStatus = JINGLE_STATUS_FAILED; this.disconnectSocks5Connections(); if (this.transport != null && this.transport instanceof JingleInbandTransport) { @@ -862,7 +871,8 @@ public class JingleConnection implements Transferable { this.mXmppConnectionService.updateConversationUi(); } else { this.mXmppConnectionService.markMessage(this.message, - Message.STATUS_SEND_FAILED); + Message.STATUS_SEND_FAILED, + errorMessage); this.message.setTransferable(null); } } diff --git a/src/main/res/drawable-hdpi/ic_action_camera.png b/src/main/res/drawable-hdpi/ic_action_camera.png Binary files differnew file mode 100644 index 00000000..60f9f100 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_action_camera.png diff --git a/src/main/res/drawable-hdpi/ic_camera_alt_white_24dp.png b/src/main/res/drawable-hdpi/ic_camera_alt_white_24dp.png Binary files differnew file mode 100644 index 00000000..497c88ca --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_camera_alt_white_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_verified_fingerprint.png b/src/main/res/drawable-hdpi/ic_verified_fingerprint.png Binary files differnew file mode 100644 index 00000000..8f21db57 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_verified_fingerprint.png 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 differindex 605d9a39..9d59123e 100644 --- a/src/main/res/drawable-hdpi/message_bubble_received.9.png +++ b/src/main/res/drawable-hdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-hdpi/message_bubble_received_dark.9.png b/src/main/res/drawable-hdpi/message_bubble_received_dark.9.png Binary files differindex 84d56bc8..5fd16bda 100644 --- a/src/main/res/drawable-hdpi/message_bubble_received_dark.9.png +++ b/src/main/res/drawable-hdpi/message_bubble_received_dark.9.png diff --git a/src/main/res/drawable-hdpi/message_bubble_received_grey.9.png b/src/main/res/drawable-hdpi/message_bubble_received_grey.9.png Binary files differindex b6b40f91..f1eb95ac 100644 --- a/src/main/res/drawable-hdpi/message_bubble_received_grey.9.png +++ b/src/main/res/drawable-hdpi/message_bubble_received_grey.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 differindex e0db0b1d..118b532a 100644 --- a/src/main/res/drawable-hdpi/message_bubble_received_warning.9.png +++ 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 differindex 48e3705e..314436c3 100644 --- a/src/main/res/drawable-hdpi/message_bubble_received_white.9.png +++ 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 differindex b7971a42..3570a9ef 100644 --- a/src/main/res/drawable-hdpi/message_bubble_sent.9.png +++ b/src/main/res/drawable-hdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-hdpi/message_bubble_sent_grey.9.png b/src/main/res/drawable-hdpi/message_bubble_sent_grey.9.png Binary files differindex ce6f3734..9e7319d2 100644 --- a/src/main/res/drawable-hdpi/message_bubble_sent_grey.9.png +++ b/src/main/res/drawable-hdpi/message_bubble_sent_grey.9.png diff --git a/src/main/res/drawable-mdpi/ic_action_camera.png b/src/main/res/drawable-mdpi/ic_action_camera.png Binary files differnew file mode 100644 index 00000000..75d6b8c2 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_action_camera.png diff --git a/src/main/res/drawable-mdpi/ic_camera_alt_white_24dp.png b/src/main/res/drawable-mdpi/ic_camera_alt_white_24dp.png Binary files differnew file mode 100644 index 00000000..e8305220 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_camera_alt_white_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_verified_fingerprint.png b/src/main/res/drawable-mdpi/ic_verified_fingerprint.png Binary files differnew file mode 100644 index 00000000..4ce5e60f --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_verified_fingerprint.png 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 differindex e2f06355..bff27fee 100644 --- a/src/main/res/drawable-mdpi/message_bubble_received.9.png +++ b/src/main/res/drawable-mdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-mdpi/message_bubble_received_dark.9.png b/src/main/res/drawable-mdpi/message_bubble_received_dark.9.png Binary files differindex eb768233..a964773e 100644 --- a/src/main/res/drawable-mdpi/message_bubble_received_dark.9.png +++ b/src/main/res/drawable-mdpi/message_bubble_received_dark.9.png diff --git a/src/main/res/drawable-mdpi/message_bubble_received_grey.9.png b/src/main/res/drawable-mdpi/message_bubble_received_grey.9.png Binary files differindex 7cd4ca33..a5424c5d 100644 --- a/src/main/res/drawable-mdpi/message_bubble_received_grey.9.png +++ b/src/main/res/drawable-mdpi/message_bubble_received_grey.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 differindex 16d468b4..cf57722a 100644 --- a/src/main/res/drawable-mdpi/message_bubble_received_warning.9.png +++ 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 differindex febb8bfb..444d9852 100644 --- a/src/main/res/drawable-mdpi/message_bubble_received_white.9.png +++ 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 differindex cd891b0f..fafa156a 100644 --- a/src/main/res/drawable-mdpi/message_bubble_sent.9.png +++ b/src/main/res/drawable-mdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-mdpi/message_bubble_sent_grey.9.png b/src/main/res/drawable-mdpi/message_bubble_sent_grey.9.png Binary files differindex 240b1237..1275668c 100644 --- a/src/main/res/drawable-mdpi/message_bubble_sent_grey.9.png +++ b/src/main/res/drawable-mdpi/message_bubble_sent_grey.9.png diff --git a/src/main/res/drawable-xhdpi/ic_action_camera.png b/src/main/res/drawable-xhdpi/ic_action_camera.png Binary files differnew file mode 100644 index 00000000..31b4b221 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_action_camera.png diff --git a/src/main/res/drawable-xhdpi/ic_camera_alt_white_24dp.png b/src/main/res/drawable-xhdpi/ic_camera_alt_white_24dp.png Binary files differnew file mode 100644 index 00000000..be9fb226 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_camera_alt_white_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_verified_fingerprint.png b/src/main/res/drawable-xhdpi/ic_verified_fingerprint.png Binary files differnew file mode 100644 index 00000000..ad03a41b --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_verified_fingerprint.png 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 differindex b950cb91..3796e964 100644 --- a/src/main/res/drawable-xhdpi/message_bubble_received.9.png +++ b/src/main/res/drawable-xhdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-xhdpi/message_bubble_received_dark.9.png b/src/main/res/drawable-xhdpi/message_bubble_received_dark.9.png Binary files differindex f29649d8..800ea806 100644 --- a/src/main/res/drawable-xhdpi/message_bubble_received_dark.9.png +++ b/src/main/res/drawable-xhdpi/message_bubble_received_dark.9.png diff --git a/src/main/res/drawable-xhdpi/message_bubble_received_grey.9.png b/src/main/res/drawable-xhdpi/message_bubble_received_grey.9.png Binary files differindex b9fb7957..5fce3e04 100644 --- a/src/main/res/drawable-xhdpi/message_bubble_received_grey.9.png +++ b/src/main/res/drawable-xhdpi/message_bubble_received_grey.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 differindex 18d36ed5..12ecb806 100644 --- a/src/main/res/drawable-xhdpi/message_bubble_received_warning.9.png +++ 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 differindex b005d170..016e1485 100644 --- a/src/main/res/drawable-xhdpi/message_bubble_received_white.9.png +++ 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 differindex 8f2fd571..f15dbf53 100644 --- a/src/main/res/drawable-xhdpi/message_bubble_sent.9.png +++ b/src/main/res/drawable-xhdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-xhdpi/message_bubble_sent_grey.9.png b/src/main/res/drawable-xhdpi/message_bubble_sent_grey.9.png Binary files differindex af6c7608..c233a4ae 100644 --- a/src/main/res/drawable-xhdpi/message_bubble_sent_grey.9.png +++ b/src/main/res/drawable-xhdpi/message_bubble_sent_grey.9.png diff --git a/src/main/res/drawable-xxhdpi/ic_action_camera.png b/src/main/res/drawable-xxhdpi/ic_action_camera.png Binary files differnew file mode 100644 index 00000000..e7b99102 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_action_camera.png diff --git a/src/main/res/drawable-xxhdpi/ic_camera_alt_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_camera_alt_white_24dp.png Binary files differnew file mode 100644 index 00000000..c8e69dce --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_camera_alt_white_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_verified_fingerprint.png b/src/main/res/drawable-xxhdpi/ic_verified_fingerprint.png Binary files differnew file mode 100644 index 00000000..5936da96 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_verified_fingerprint.png 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 differindex 3dd99562..7b9fbb59 100644 --- a/src/main/res/drawable-xxhdpi/message_bubble_received.9.png +++ b/src/main/res/drawable-xxhdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-xxhdpi/message_bubble_received_dark.9.png b/src/main/res/drawable-xxhdpi/message_bubble_received_dark.9.png Binary files differindex 2cf9f699..043c84c6 100644 --- a/src/main/res/drawable-xxhdpi/message_bubble_received_dark.9.png +++ b/src/main/res/drawable-xxhdpi/message_bubble_received_dark.9.png diff --git a/src/main/res/drawable-xxhdpi/message_bubble_received_grey.9.png b/src/main/res/drawable-xxhdpi/message_bubble_received_grey.9.png Binary files differindex 20ae5f18..d9e42577 100644 --- a/src/main/res/drawable-xxhdpi/message_bubble_received_grey.9.png +++ b/src/main/res/drawable-xxhdpi/message_bubble_received_grey.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 differindex 10130239..652b5986 100644 --- a/src/main/res/drawable-xxhdpi/message_bubble_received_warning.9.png +++ 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 differindex e2bb6f80..e6f13f90 100644 --- a/src/main/res/drawable-xxhdpi/message_bubble_received_white.9.png +++ 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 differindex 31c5fa13..03ef731a 100644 --- a/src/main/res/drawable-xxhdpi/message_bubble_sent.9.png +++ b/src/main/res/drawable-xxhdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-xxhdpi/message_bubble_sent_grey.9.png b/src/main/res/drawable-xxhdpi/message_bubble_sent_grey.9.png Binary files differindex 2c486407..13191905 100644 --- a/src/main/res/drawable-xxhdpi/message_bubble_sent_grey.9.png +++ b/src/main/res/drawable-xxhdpi/message_bubble_sent_grey.9.png diff --git a/src/main/res/drawable-xxxhdpi/ic_camera_alt_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_camera_alt_white_24dp.png Binary files differnew file mode 100644 index 00000000..777658e9 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_camera_alt_white_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_verified_fingerprint.png b/src/main/res/drawable-xxxhdpi/ic_verified_fingerprint.png Binary files differnew file mode 100644 index 00000000..c738090e --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_verified_fingerprint.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 differindex 1620e8bc..2a62f258 100644 --- a/src/main/res/drawable-xxxhdpi/message_bubble_received.9.png +++ b/src/main/res/drawable-xxxhdpi/message_bubble_received.9.png diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_received_dark.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_received_dark.9.png Binary files differindex a62faee5..10e742ff 100644 --- a/src/main/res/drawable-xxxhdpi/message_bubble_received_dark.9.png +++ b/src/main/res/drawable-xxxhdpi/message_bubble_received_dark.9.png diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_received_grey.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_received_grey.9.png Binary files differindex 5e9b6b2d..467d974b 100644 --- a/src/main/res/drawable-xxxhdpi/message_bubble_received_grey.9.png +++ b/src/main/res/drawable-xxxhdpi/message_bubble_received_grey.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 differindex 99ec1efa..74a12c19 100644 --- a/src/main/res/drawable-xxxhdpi/message_bubble_received_warning.9.png +++ 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 differindex 333aaf68..3d729209 100644 --- a/src/main/res/drawable-xxxhdpi/message_bubble_received_white.9.png +++ 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 differindex f82c72a2..698580f7 100644 --- a/src/main/res/drawable-xxxhdpi/message_bubble_sent.9.png +++ b/src/main/res/drawable-xxxhdpi/message_bubble_sent.9.png diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_sent_grey.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_sent_grey.9.png Binary files differindex ff6e8349..3e2c9fe7 100644 --- a/src/main/res/drawable-xxxhdpi/message_bubble_sent_grey.9.png +++ b/src/main/res/drawable-xxxhdpi/message_bubble_sent_grey.9.png diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml index 64393b25..6c26a162 100644 --- a/src/main/res/layout/activity_edit_account.xml +++ b/src/main/res/layout/activity_edit_account.xml @@ -163,7 +163,7 @@ </RelativeLayout> <RelativeLayout - android:id="@+id/battery_optimization" + android:id="@+id/os_optimization" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/activity_vertical_margin" @@ -175,7 +175,7 @@ android:padding="@dimen/infocard_padding" android:visibility="gone"> <TextView - android:id="@+id/batt_op_headline" + android:id="@+id/os_optimization_headline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/battery_optimizations_enabled" @@ -183,25 +183,27 @@ android:textSize="?attr/TextSizeHeadline" android:textStyle="bold"/> <TextView - android:id="@+id/batt_op_body" + android:id="@+id/os_optimization_body" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_below="@+id/batt_op_headline" + android:layout_below="@+id/os_optimization_headline" android:layout_marginBottom="8dp" android:layout_marginTop="8dp" android:text="@string/battery_optimizations_enabled_explained" android:textColor="?attr/color_text_primary" android:textSize="?attr/TextSizeBody"/> <Button - android:id="@+id/batt_op_disable" + android:id="@+id/os_optimization_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"/> + android:layout_alignParentRight="true" + android:layout_below="@+id/os_optimization_body" + android:text="@string/disable" + android:textColor="@color/accent"/> </RelativeLayout> @@ -608,6 +610,14 @@ android:orientation="vertical" android:showDividers="middle"> </LinearLayout> + <Button + android:id="@+id/clear_devices" + style="?android:attr/borderlessButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/clear_other_devices" + android:layout_gravity="center_horizontal" + android:textColor="@color/accent"/> </LinearLayout> </LinearLayout> </ScrollView> diff --git a/src/main/res/layout/contact_key.xml b/src/main/res/layout/contact_key.xml index 7076dad2..c16d5e08 100644 --- a/src/main/res/layout/contact_key.xml +++ b/src/main/res/layout/contact_key.xml @@ -1,37 +1,40 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" - android:layout_height="match_parent" > + android:layout_height="match_parent" + android:longClickable="true"> <RelativeLayout android:id="@+id/key_data" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" - android:paddingTop="8dp" + android:paddingBottom="8dp" android:paddingLeft="8dp" - android:paddingBottom="8dp"> + android:paddingTop="8dp"> <TextView android:id="@+id/key" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="?attr/color_text_primary" android:layout_alignParentLeft="true" - android:layout_toLeftOf="@+id/tgl_trust" + android:layout_toLeftOf="@+id/action_container" + android:fontFamily="monospace" + android:textColor="?attr/color_text_primary" android:textSize="?attr/TextSizeBody" android:typeface="monospace" - android:fontFamily="monospace"/> + android:longClickable="true"/> <TextView android:id="@+id/key_type" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="?attr/color_text_secondary" android:layout_alignParentLeft="true" android:layout_below="@+id/key" android:maxLines="1" - android:textSize="?attr/TextSizeInfo"/> + android:textColor="?attr/color_text_secondary" + android:textSize="?attr/TextSizeInfo" + android:longClickable="true"/> <TextView android:id="@+id/key_trust" @@ -39,32 +42,46 @@ android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_below="@+id/key" - android:visibility="gone" android:textColor="?attr/color_text_secondary" - android:textSize="?attr/TextSizeInfo"/> - - <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:alpha="?attr/icon_alpha" - android:src="?attr/icon_remove" - android:visibility="gone" /> + android:textSize="?attr/TextSizeInfo" + android:visibility="gone" + android:longClickable="true"/> + <LinearLayout + android:id="@+id/action_container" + android:layout_width="96dp" + android:layout_marginRight="-32dp" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_alignParentRight="true" + android:layout_centerVertical="true"> + <ImageButton + android:layout_gravity="center_horizontal" + android:id="@+id/button_remove" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:alpha="?attr/icon_alpha" + android:background="?android:selectableItemBackground" + android:padding="@dimen/image_button_padding" + android:src="?attr/icon_remove" + android:layout_marginRight="16dp" + android:visibility="gone" /> + <ImageView + android:visibility="gone" + android:id="@+id/verified_fingerprint" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginRight="16dp" + android:src="@drawable/ic_verified_fingerprint" /> - <eu.siacs.conversations.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"/> + <eu.siacs.conversations.ui.widget.Switch + android:id="@+id/tgl_trust" + style="@style/MD" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" /> + </LinearLayout> </RelativeLayout> </RelativeLayout>
\ 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 466dd045..ea098b2d 100644 --- a/src/main/res/layout/message_received.xml +++ b/src/main/res/layout/message_received.xml @@ -45,14 +45,16 @@ android:layout_marginTop="8dp" android:layout_marginBottom="4dp" android:adjustViewBounds="true" + android:longClickable="true" android:background="@color/black87" android:scaleType="centerCrop" /> - <TextView + <eu.siacs.conversations.ui.widget.CopyTextView android:id="@+id/message_body" android:layout_width="wrap_content" android:layout_height="wrap_content" android:autoLink="web" + android:longClickable="true" android:textColorLink="@color/white" android:textColor="@color/white" android:textColorHighlight="@color/grey800" @@ -61,6 +63,7 @@ <Button android:id="@+id/download_button" style="?android:attr/buttonStyleSmall" + android:longClickable="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" /> diff --git a/src/main/res/layout/message_sent.xml b/src/main/res/layout/message_sent.xml index 37ab9da1..0f81c3f7 100644 --- a/src/main/res/layout/message_sent.xml +++ b/src/main/res/layout/message_sent.xml @@ -47,13 +47,15 @@ android:layout_marginBottom="4dp" android:adjustViewBounds="true" android:background="@color/black87" + android:longClickable="true" android:scaleType="centerCrop" /> - <TextView + <eu.siacs.conversations.ui.widget.CopyTextView android:id="@+id/message_body" android:layout_width="wrap_content" android:layout_height="wrap_content" android:autoLink="web" + android:longClickable="true" android:textColorLink="@color/black87" android:textColor="?attr/color_text_primary" android:textColorHighlight="@color/grey500" @@ -64,6 +66,7 @@ style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:longClickable="true" android:visibility="gone" /> <LinearLayout diff --git a/src/main/res/menu/editaccount.xml b/src/main/res/menu/editaccount.xml index 9ab1788b..c67e5c47 100644 --- a/src/main/res/menu/editaccount.xml +++ b/src/main/res/menu/editaccount.xml @@ -1,5 +1,21 @@ <menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/action_share" + android:title="@string/share_uri_with" + android:icon="?attr/icon_share" + android:showAsAction="always"> + <menu> + <item + android:id="@+id/action_share_barcode" + android:title="@string/share_as_barcode"/> + <item + android:id="@+id/action_share_uri" + android:title="@string/share_as_uri"/> + <item android:id="@+id/action_share_http" + android:title="@string/share_as_http"/> + </menu> + </item> + <item android:id="@+id/action_change_presence" android:showAsAction="always" @@ -44,10 +60,6 @@ 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" diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml index bc8acede..283d3f2f 100644 --- a/src/main/res/menu/message_context.xml +++ b/src/main/res/menu/message_context.xml @@ -5,6 +5,10 @@ android:id="@+id/copy_text" android:title="@string/copy_text" android:visible="false"/> + <item + android:id="@+id/select_text" + android:title="@string/select_text" + android:visible="false"/> <item android:id="@+id/retry_decryption" android:title="Retry decryption" @@ -22,6 +26,10 @@ android:title="@string/copy_original_url" android:visible="false"/> <item + android:id="@+id/show_error_message" + android:title="@string/show_error_message" + android:visible="false"/> + <item android:id="@+id/send_again" android:title="@string/send_again" android:visible="false"/> diff --git a/src/main/res/menu/omemo_key_context.xml b/src/main/res/menu/omemo_key_context.xml new file mode 100644 index 00000000..1e825902 --- /dev/null +++ b/src/main/res/menu/omemo_key_context.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/verify_scan" + android:title="@string/scan_qr_code" + /> + <item + android:id="@+id/purge_omemo_key" + android:title="@string/purge_key"/> + <item + android:id="@+id/copy_omemo_key" + android:title="@string/copy_fingerprint"/> +</menu>
\ No newline at end of file diff --git a/src/main/res/menu/trust_keys.xml b/src/main/res/menu/trust_keys.xml new file mode 100644 index 00000000..90304767 --- /dev/null +++ b/src/main/res/menu/trust_keys.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@+id/action_scan_qr_code" + android:title="@string/scan_qr_code" + android:showAsAction="always" + android:icon="?attr/icon_scan_qr_code"/> +</menu>
\ No newline at end of file diff --git a/src/main/res/values-ar/strings.xml b/src/main/res/values-ar/strings.xml index b54de99c..5de991c3 100644 --- a/src/main/res/values-ar/strings.xml +++ b/src/main/res/values-ar/strings.xml @@ -100,7 +100,6 @@ <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> @@ -223,7 +222,6 @@ <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> diff --git a/src/main/res/values-bg/strings.xml b/src/main/res/values-bg/strings.xml index 868ea5e2..f0054b7c 100644 --- a/src/main/res/values-bg/strings.xml +++ b/src/main/res/values-bg/strings.xml @@ -122,14 +122,14 @@ <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_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> <string name="error">Възникна грешка</string> <string name="pref_grant_presence_updates">Позволяване на актуализации на присъствието</string> - <string name="pref_grant_presence_updates_summary">Предварително позволяване и изискване на абониране за актуализации на присъствието за контакти, създадено от Вас</string> + <string name="pref_grant_presence_updates_summary">Предварително позволяване и изискване на абонамент за присъствието за контактите, създадени от Вас</string> <string name="subscriptions">Абонаменти</string> <string name="your_account">Вашият профил</string> <string name="keys">Ключове</string> @@ -283,6 +283,7 @@ <string name="conference_requires_password">Беседата изисква парола</string> <string name="enter_password">Въведете парола</string> <string name="missing_presence_updates">Липсват актуализации за присъствието на контакта</string> + <string name="missing_presence_subscription">Липсва абонамент за присъствието</string> <string name="request_presence_updates">Моля, първо помолете контакта за актуализации на присъствието му.\n\n<small>Това ще бъде използвано, за да се провери какъв клиент (или клиенти) използва контакта.</small></string> <string name="request_now">Поискване сега</string> <string name="delete_fingerprint">Изтриване на отпечатъка</string> @@ -330,6 +331,7 @@ <string name="check_x_filesize_on_host">Проверете размера на %1$s на %2$s</string> <string name="message_options">Настройки за съобщенята</string> <string name="copy_text">Копиране на текста</string> + <string name="select_text">Избиране на текста</string> <string name="copy_original_url">Копиране на оригиналния адрес</string> <string name="send_again">Повторно изпращане</string> <string name="file_url">Адрес на файла</string> @@ -337,8 +339,8 @@ <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="scan_qr_code">Сканиране на 2-измерен баркод</string> + <string name="show_qr_code">Показване на 2-измерен баркод</string> <string name="show_block_list">Показване на списъка с блокирани</string> <string name="account_details">Подробности за профила</string> <string name="verify_otr">Проверка на OTR</string> @@ -378,7 +380,7 @@ <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> @@ -471,7 +473,7 @@ <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="pref_chat_states_summary">Така контактите Ви ще разбират, когато им пишете съобщения</string> <string name="send_location">Изпращане на местоположението</string> <string name="show_location">Показване на местоположението</string> <string name="no_application_found_to_display_location">Няма намерено приложение за показване на местоположението</string> @@ -651,10 +653,33 @@ <string name="unable_to_update_account">Неуспешно обновяване на профила</string> <string name="missing_presence_subscription_with_x">Липсва абонамент за присъствието на %s.</string> <string name="missing_keys_from_x">Липсват ключове OMEMO от %s.</string> + <string name="missing_omemo_keys">Липсват ключове OMEMO</string> <string name="wrong_conference_configuration">Това не е лична, не-анонимна беседа.</string> <string name="this_conference_has_no_members">Няма участници в тази беседа.</string> <string name="report_jid_as_spammer">Докладване на този JID за изпращане на нежелани съобщения.</string> <string name="pref_delete_omemo_identities">Изтриване на идентификаторите OMEMO</string> <string name="pref_delete_omemo_identities_summary">Пресъздайте своите ключове OMEMO. Всички Ваши контакти ще трябва да Ви потвърдят отново. Използвайте това само в краен случай.</string> <string name="delete_selected_keys">Изтриване на избраните ключове.</string> + <string name="error_publish_avatar_offline">Трябва да бъдете свързан(а), за да публикувате аватара си.</string> + <string name="show_error_message">Показване на грешка</string> + <string name="error_message">Съобщение за грешка</string> + <string name="data_saver_enabled">Съхранението на данни е включено</string> + <string name="data_saver_enabled_explained">Операционната Ви система не позволява на Conversations да се свързва с Интернет когато работи на заден фон. За да получавате известия за новите съобщения, трябва да дадете на Conversations неограничен достъп когато съхранението на данни е включено.\nConversations ще продължи да се опитва да записва данните когато е възможно.</string> + <string name="device_does_not_support_data_saver">Устройството Ви не поддържа изключването на съхранението на данни за Conversations.</string> + <string name="error_unable_to_create_temporary_file">Неуспешно създаване на временен файл</string> + <string name="this_device_has_been_verified">Това устройство е потвърдено</string> + <string name="copy_fingerprint">Копиране на отпечатъка</string> + <string name="all_omemo_keys_have_been_verified">Всички ключове OMEMO са потвърдени</string> + <string name="barcode_does_not_contain_fingerprints_for_this_conversation">Баркодът не съдържа отпечатъци за този разговор.</string> + <string name="verified_fingerprints">Потвърдени отпечатъци</string> + <string name="use_camera_icon_to_scan_barcode">Използвайте камерата, за да сканирате баркода на контакт</string> + <string name="please_wait_for_keys_to_be_fetched">Моля, изчакайте получаването на ключовете</string> + <string name="share_as_barcode">Споделяне като баркод</string> + <string name="share_as_uri">Споделяне като адрес на XMPP</string> + <string name="share_as_http">Споделяне като връзка в Интернет</string> + <string name="pref_blind_trust_before_verification">Доверяване на сляпо преди потвърждение</string> + <string name="pref_blind_trust_before_verification_summary">Автоматично доверяване на всички нови устройства на контактите, които не са били потвърдени преди, и питане за ръчно одобрение всеки път, когато потвърден контакт добави ново устройство.</string> + <string name="blindly_trusted_omemo_keys">Приети на сляпо ключове OMEMO</string> + <string name="not_trusted">Неприети</string> + <string name="invalid_barcode">Грешен 2-измерен баркод</string> </resources> diff --git a/src/main/res/values-ca/strings.xml b/src/main/res/values-ca/strings.xml index b2f051e9..6f5791aa 100644 --- a/src/main/res/values-ca/strings.xml +++ b/src/main/res/values-ca/strings.xml @@ -98,13 +98,11 @@ <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="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> <string name="error">Un error ha passat</string> <string name="pref_grant_presence_updates">Concedir actualitzacions</string> - <string name="pref_grant_presence_updates_summary">Preventivament atorgar i preguntar per les subscripcions als contactes creats</string> <string name="subscriptions">Subscripcions</string> <string name="your_account">El teu compte</string> <string name="keys">Claus</string> @@ -273,8 +271,6 @@ <string name="url_copied_to_clipboard">URL copiada al portapapers</string> <string name="message_copied_to_clipboard">Missatge copiat al portapapers</string> <string name="image_transmission_failed">Transmissió de l\'imatge fallada</string> - <string name="scan_qr_code">Escanejar el codi QR</string> - <string name="show_qr_code">Mostrar el codi QR</string> <string name="show_block_list">Mostra la llista de bloqueig</string> <string name="account_details">Detalls del compte</string> <string name="verify_otr">Verificar OTR</string> @@ -310,7 +306,6 @@ <string name="no_application_found_to_open_file">Cap aplicació trobada a l\'obrir l\'arxiu</string> <string name="could_not_verify_fingerprint">No s\'ha pogut verificar l\'empremta dactilar</string> <string name="manually_verify">Verificat manualment</string> - <string name="are_you_sure_verify_fingerprint">Estàs segur que vols verificar l\'empremta digital OTR dels teus contactes?</string> <string name="pref_show_dynamic_tags">Mostrar etiquetes dinàmiques</string> <string name="pref_show_dynamic_tags_summary">Mostra etiquetes de nomès lectura per sota dels noms dels contactes</string> <string name="enable_notifications">Habilitar notificació</string> @@ -386,7 +381,6 @@ <string name="disable_account">Deshabilita el compte</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> <string name="send_location">Enviar localització</string> <string name="show_location">Mostrar localització</string> <string name="no_application_found_to_display_location">No s\'ha trobat cap aplicació per mostrar la localització</string> diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 54af09db..c18a6346 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -122,14 +122,12 @@ <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">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> <string name="error">Došlo k chybě</string> <string name="pref_grant_presence_updates">Povolit aktualizace stavu</string> - <string name="pref_grant_presence_updates_summary">Aktivně povolovat a žádat o zasílání změn stavu pro vytvářené kontakty</string> <string name="subscriptions">Odběry</string> <string name="your_account">Váš účet</string> <string name="keys">Klíče</string> @@ -331,6 +329,7 @@ <string name="check_x_filesize_on_host">Kontrola %1$s velikosti na %2$s</string> <string name="message_options">Možnosti zpráv</string> <string name="copy_text">Zkopírovat text</string> + <string name="select_text">Vybrat text</string> <string name="copy_original_url">Kopírovat originální URL</string> <string name="send_again">Poslat znovu</string> <string name="file_url">URL souboru</string> @@ -338,8 +337,6 @@ <string name="url_copied_to_clipboard">URL zkopírováno do schránky</string> <string name="message_copied_to_clipboard">Zpráva zkopírována do schránky</string> <string name="image_transmission_failed">Přenos obrázku selhal</string> - <string name="scan_qr_code">Skenovat QR kód</string> - <string name="show_qr_code">Zobrazit QR kód</string> <string name="show_block_list">Zobrazit seznam blokovaných</string> <string name="account_details">Detaily účtu</string> <string name="verify_otr">Ověřit OTR</string> @@ -379,7 +376,6 @@ <string name="no_application_found_to_open_file">Nebyla nalezena aplikace umožňující otevření souboru</string> <string name="could_not_verify_fingerprint">Ověření otisku se nezdařilo</string> <string name="manually_verify">Ruční ověření</string> - <string name="are_you_sure_verify_fingerprint">Opravdu si přejete ověřit OTR otisk kontaktu?</string> <string name="pref_show_dynamic_tags">Zobrazit dynamické tagy</string> <string name="pref_show_dynamic_tags_summary">Zobrazit tagy pro čtení pod kontakty</string> <string name="enable_notifications">Povolit upozornění</string> @@ -472,7 +468,6 @@ <string name="contact_is_typing">%s píše...</string> <string name="contact_has_stopped_typing">%s přestal(a) psát</string> <string name="pref_chat_states">Upozornění při psaní</string> - <string name="pref_chat_states_summary">Oznamovat kontaktům že píšete novou zprávu</string> <string name="send_location">Poslat pozici</string> <string name="show_location">Zobrazit pozici</string> <string name="no_application_found_to_display_location">Nebyla nalezena aplikace pro zobrazení pozice</string> @@ -663,4 +658,13 @@ <string name="pref_delete_omemo_identities_summary">Znovu vygenerovat OMEMO klíče. Vyžaduje potvrzení od všech vašich kontaktů. POužijte pouze jako poslední řešení.</string> <string name="delete_selected_keys">Smazat vybrané klíče</string> <string name="error_publish_avatar_offline">Pro zveřejnění svého avatara musíte být online.</string> + <string name="show_error_message">Zobrazit chybovou zprávu</string> + <string name="error_message">Chybová zpráva</string> + <string name="data_saver_enabled">Zapnuta úspora dat</string> + <string name="data_saver_enabled_explained">Váš operační systém zabraňuje aplikaci Conversations v přístupu na Internet, pokud tato běží na pozadí. Pro příjem upozornění na nové zprávy musíte Conversations povolit neomezený přístup při zapnuté úspoře dat.\nConversations se bude i přesto snažit omezovat přenos dat.</string> + <string name="device_does_not_support_data_saver">Tento přístroj nepodporuje vypnutí šetření dat pro aplikaci Conversations.</string> + <string name="error_unable_to_create_temporary_file">Nelze vytvořit dočasný soubor</string> + <string name="this_device_has_been_verified">Tento přístroj byl ověřen</string> + <string name="copy_fingerprint">Kopírovat identifikátor</string> + <string name="all_omemo_keys_have_been_verified">Všechny OMEMO klíče byly ověřeny</string> </resources> diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index 962a6b3b..93414f04 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -4,8 +4,8 @@ <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_contact_details">Kontaktdetails</string> + <string name="action_muc_details">Konferenzdetails</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> @@ -17,8 +17,8 @@ <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_conference_details">Konferenzdetails</string> + <string name="title_activity_contact_details">Kontaktdetails</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> @@ -220,7 +220,7 @@ <string name="reception_failed">Empfang fehlgeschlagen</string> <string name="your_fingerprint">Dein Fingerabdruck</string> <string name="otr_fingerprint">OTR-Fingerabdruck</string> - <string name="otr_fingerprint_selected_message">OTR Fingerabdruck der Nachricht</string> + <string name="otr_fingerprint_selected_message">OTR-Fingerabdruck der Nachricht</string> <string name="openpgp_key_id">OpenPGP Schlüssel-ID</string> <string name="omemo_fingerprint">OMEMO-Fingerabdruck</string> <string name="omemo_fingerprint_x509">v\\OMEMO-Fingerabdruck</string> @@ -239,14 +239,14 @@ <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="view_contact_details">Kontaktdetails anzeigen</string> <string name="block_contact">Kontakt sperren</string> <string name="unblock_contact">Kontakt entsperren</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">Konferenzadresse</string> <string name="conference_address_example">raum@conference.beispiel.de/Nickname</string> <string name="save_as_bookmark">Zur Kontaktliste hinzufügen</string> <string name="delete_bookmark">Von Kontaktliste entfernen</string> @@ -313,7 +313,7 @@ <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">Konferenzname</string> <string name="pref_conference_name_summary">Thema von Konferenzen anstatt JID als Namen verwenden</string> <string name="pref_autojoin">Konferenzen automatisch beitreten</string> <string name="pref_autojoin_summary">Autojoin-Flag in Konferenzlesezeichen beachten</string> @@ -331,17 +331,18 @@ <string name="check_x_filesize_on_host">%1$s-Größe auf %2$s prüfen</string> <string name="message_options">Nachrichtenoptionen</string> <string name="copy_text">Text kopieren</string> + <string name="select_text">Text markieren</string> <string name="copy_original_url">Original-URL kopieren</string> <string name="send_again">Erneut senden</string> <string name="file_url">Datei-URL</string> <string name="message_text">Nachrichtentext</string> <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">QR-Code scannen</string> - <string name="show_qr_code">QR-Code anzeigen</string> + <string name="image_transmission_failed">Bildübertragung fehlgeschlagen</string> + <string name="scan_qr_code">Barcode scannen</string> + <string name="show_qr_code">Barcode anzeigen</string> <string name="show_block_list">Sperrliste anzeigen</string> - <string name="account_details">Konto-Details</string> + <string name="account_details">Kontodetails</string> <string name="verify_otr">OTR prüfen</string> <string name="remote_fingerprint">Fingerabdruck der Gegenseite</string> <string name="scan">Scannen</string> @@ -384,7 +385,7 @@ <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="no_conference_server_found">Kein Konferenzserver gefunden</string> <string name="conference_creation_failed">Erstellen der Konferenz fehlgeschlagen!</string> <string name="secret_accepted">Schlüssel akzeptiert!</string> <string name="reset">Zurücksetzen</string> @@ -409,7 +410,7 @@ <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="smp_explain_answer">Dein Kontakt möchte deinen Fingerabdruck mit Hilfe eines gemeinsamen Geheimnisses ü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> @@ -437,13 +438,13 @@ <string name="could_not_change_role">Rolle von %s konnte nicht geändert werden</string> <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="conference_options">Konferenzoptionen</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="modified_conference_options">Konferenzoptionen wurden modifiziert!</string> + <string name="could_not_modify_conference_options">Konferenzoptionen konnten nicht modifiziert werden</string> <string name="never">Niemals</string> <string name="thirty_minutes">30 Minuten</string> <string name="one_hour">1 Stunde</string> @@ -472,7 +473,7 @@ <string name="contact_is_typing">%s schreibt…</string> <string name="contact_has_stopped_typing">%s schreibt nicht mehr</string> <string name="pref_chat_states">Tipp-Benachrichtigung</string> - <string name="pref_chat_states_summary">Informiere deine Kontakte, wenn du eine Nachricht eintippst.</string> + <string name="pref_chat_states_summary">Informiere deine Kontakte, wenn du eine Nachricht schreibst</string> <string name="send_location">Standort senden</string> <string name="show_location">Standort anzeigen</string> <string name="no_application_found_to_display_location">Keine App für die Standort-Anzeige gefunden</string> @@ -507,8 +508,8 @@ <string name="username">Benutzername</string> <string name="username_hint">Benutzername</string> <string name="invalid_username">Ungültiger Benutzername</string> - <string name="conference_name">Konferenz-Name</string> - <string name="invalid_conference_name">Dies ist kein gültiger Konferenz-Name</string> + <string name="conference_name">Konferenzname</string> + <string name="invalid_conference_name">Dies ist kein gültiger Konferenzname</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> @@ -526,7 +527,7 @@ <string name="pref_xa_on_silent_mode_summary">Setzt deinen Status auf \"nicht verfügbar\", solange dein Gerät lautlos ist</string> <string name="pref_treat_vibrate_as_silent">Vibration als Lautlos behandeln</string> <string name="pref_treat_vibrate_as_silent_summary">Setzt deinen Status auf \"nicht verfügbar\", solange das Gerät auf Vibration geschaltet ist</string> - <string name="pref_show_connection_options">Erweiterte Verbindungs-Optionen</string> + <string name="pref_show_connection_options">Erweiterte Verbindungsoptionen</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> @@ -541,11 +542,11 @@ <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="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="pref_use_tor">Über TOR verbinden</string> + <string name="pref_use_tor">Über Tor verbinden</string> <string name="pref_use_tor_summary">Alle Verbindungen über das Tor-Netzwerk tunneln. Benötigt Orbot</string> <string name="account_settings_hostname">Hostname</string> <string name="account_settings_port">Port</string> @@ -596,7 +597,7 @@ <string name="security_error_invalid_file_access">Sicherheitsfehler: Dateizugriff nicht erlaubt</string> <string name="no_application_to_share_uri">Keine Anwendung zum Teilen der URI gefunden</string> <string name="share_uri_with">Teile URI mit…</string> - <string name="welcome_text">XMPP ist ein providerunabhängiges Protokoll. Du kannst diesen Client mit jedem beliebigen XMPP-Server benutzen.\nUm es Dir leicht zu machen haben wir die Möglichkeit geschaffen, ein Konto auf conversations.im¹ anzulegen. Dieser Provider ist besonders gut für Conversations geeignet. </string> + <string name="welcome_text">XMPP ist ein providerunabhängiges Protokoll. Du kannst diesen Client mit jedem beliebigen XMPP-Server benutzen.\nUm es dir leicht zu machen haben wir die Möglichkeit geschaffen, ein Konto auf conversations.im¹ anzulegen. Dieser Provider ist besonders gut für Conversations geeignet. </string> <string name="magic_create_text">Wir führen dich durch den Prozess der Kontoerstellung auf conversations.im.¹\nWenn du conversations.im als Provider wählst, kannst du mit Nutzern anderer Anbieter kommunizieren, indem du ihnen deine vollständige Jabber-ID gibst.</string> <string name="your_full_jid_will_be">Deine vollständige Jabber ID lautet: %s</string> <string name="create_account">Konto erstellen</string> @@ -660,4 +661,25 @@ <string name="pref_delete_omemo_identities_summary">Regeneriere deine OMEMO-Schlüssel. Alle deine Kontakte müssen dich neu verifizieren. Nutze dies als letzten Ausweg.</string> <string name="delete_selected_keys">Ausgewählte Schlüssel löschen</string> <string name="error_publish_avatar_offline">Du musst verbunden sein um deinen Avatar zu veröffentlichen</string> + <string name="show_error_message">Zeige Fehlermeldung</string> + <string name="error_message">Fehlermeldung</string> + <string name="data_saver_enabled">Datensparmodus aktiv</string> + <string name="data_saver_enabled_explained">Dein Betriebssystem verbietet Conversations im Hintergrund den Zugang zum Internet. Um Benachrichtigungen erhalten zu können, solltest du Conversations den Zugang erlauben, wenn der Datensparmodus aktiv ist. Conversations wird dennoch versuchen, so viele Daten wie möglich einzusparen.</string> + <string name="device_does_not_support_data_saver">Dein Gerät unterstützt den Datensparmodus für Conversations nicht.</string> + <string name="error_unable_to_create_temporary_file">Temporäre Datei kann nicht erstellt werden</string> + <string name="this_device_has_been_verified">Dieses Gerät wurde verifiziert</string> + <string name="copy_fingerprint">Fingerabdruck kopieren</string> + <string name="all_omemo_keys_have_been_verified">Alle OMEMO-Schlüssel wurden verifiziert</string> + <string name="barcode_does_not_contain_fingerprints_for_this_conversation">Barcode enthält keine Fingerabdrücke für diese Unterhaltung.</string> + <string name="verified_fingerprints">Überprüfte Fingerabdrücke</string> + <string name="use_camera_icon_to_scan_barcode">Nutze Kamera, um Barcodes deiner Kontakte zu scannen</string> + <string name="please_wait_for_keys_to_be_fetched">Bitte warten bis Schlüssel abgerufen werden …</string> + <string name="share_as_barcode">Als Barcode teilen</string> + <string name="share_as_uri">Als XMPP URI teilen</string> + <string name="share_as_http">Als HTTP Link teilen</string> + <string name="pref_blind_trust_before_verification">Blind vertrauen vor der Überprüfung</string> + <string name="pref_blind_trust_before_verification_summary">Vertraue automatisch allen neuen Geräten von Kontakten, die noch nicht überprüft wurden und zeige eine Aufforderung zur manuellen Bestätigung an, wenn ein verifizierter Kontakt ein neues Gerät hinzufügt.</string> + <string name="blindly_trusted_omemo_keys">Blind vertraute OMEMO-Schlüssel</string> + <string name="not_trusted">Nicht vertraut</string> + <string name="invalid_barcode">Ungültiger Barcode</string> </resources> diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml index 1c2fd858..7f419332 100644 --- a/src/main/res/values-el/strings.xml +++ b/src/main/res/values-el/strings.xml @@ -98,13 +98,11 @@ <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="openpgp_error">Το OpenKeychain ανέφερε κάποιο σφάλμα</string> <string name="error_decrypting_file">Σφάλμα εισόδου/εξόδου κατά την αποκρυπτογράφηση αρχείου</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="subscriptions">Συνδρομές</string> <string name="your_account">Ο λογαριασμός σας</string> <string name="keys">Κλειδιά</string> @@ -273,8 +271,6 @@ <string name="url_copied_to_clipboard">Η διεύθυνση URL αντιγράφηκε στο πρόχειρο</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> @@ -310,7 +306,6 @@ <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> @@ -386,7 +381,6 @@ <string name="disable_account">Απενεργοποίηση λογαριασμού</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> diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index eede5b98..6cd291aa 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -122,7 +122,7 @@ <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_summary">Permitir a tus contactos saber cuando recibes y lees un mensaje</string> + <string name="pref_confirm_messages_summary">Permitir a tus contactos saber cuando has recibido y leído sus mensajes</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> @@ -331,6 +331,7 @@ <string name="check_x_filesize_on_host">Comprobar tamaño de %1$s en %2$s</string> <string name="message_options">Opciones de mensaje</string> <string name="copy_text">Copiar texto</string> + <string name="select_text">Seleccionar texto</string> <string name="copy_original_url">Copiar URL original</string> <string name="send_again">Volver a enviar</string> <string name="file_url">URL de archivo</string> @@ -379,7 +380,7 @@ <string name="no_application_found_to_open_file">No se ha encontrado ninguna aplicación para abrir el archivo</string> <string name="could_not_verify_fingerprint">No se puede verificar el contacto</string> <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="are_you_sure_verify_fingerprint">¿Estás seguro de que quieres verificar la huella digital OTR de tu contacto?</string> <string name="pref_show_dynamic_tags">Mostrar etiquetas</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> @@ -472,7 +473,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">Permitir 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 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> @@ -660,4 +661,25 @@ <string name="pref_delete_omemo_identities_summary">Regenerar tus clave OMEMO. Todos tus contactos tendrán que verificarte de nuevo. Usa esta opción como último recurso.</string> <string name="delete_selected_keys">Eliminar claves seleccionadas</string> <string name="error_publish_avatar_offline">Debes estar conectado para publicar la imagen de perfil</string> + <string name="show_error_message">Mostrar mensaje de error</string> + <string name="error_message">Mensaje de error</string> + <string name="data_saver_enabled">Optimización de datos habilitado</string> + <string name="data_saver_enabled_explained">Tu sistema operativo está restringiendo a Conversations acceder a internet cuando está en segundo plano. Para recibir notificaciones de nuevos mensajes deberías permitir a Conversations acceder a internet cuando la optimización de datos está habilitada.\nConversations realizará igualmente optimizaciones para ahorrar datos cuando sea posible.</string> + <string name="device_does_not_support_data_saver">Tu dispositivo no soporta la opción de deshabilitar la optimización de datos para Conversations</string> + <string name="error_unable_to_create_temporary_file">No se ha podido crear el fichero temporal</string> + <string name="this_device_has_been_verified">Este dispositivo ha sido verificado</string> + <string name="copy_fingerprint">Copiar huella digital</string> + <string name="all_omemo_keys_have_been_verified">Todas las huellas digitales OMEMO han sido verificadas</string> + <string name="barcode_does_not_contain_fingerprints_for_this_conversation">El código QR no contiene huellas digitales para esta conversación.</string> + <string name="verified_fingerprints">Huellas digitales verificadas</string> + <string name="use_camera_icon_to_scan_barcode">Usa la cámara para escanear el código QR del contacto</string> + <string name="please_wait_for_keys_to_be_fetched">Por favor, espera a que las claves sean recuperadas</string> + <string name="share_as_barcode">Compartir como código QR</string> + <string name="share_as_uri">Compartir como XMPP URI</string> + <string name="share_as_http">Compartir como link HTTP</string> + <string name="pref_blind_trust_before_verification">Confianza ciega antes de verificación</string> + <string name="pref_blind_trust_before_verification_summary">Automáticamente confiar en los nuevos dispositivos de tus contactos que no han sido verificados antes y solicita confirmación manual cada vez que un contacto verificado añade un dispositivo nuevo.</string> + <string name="blindly_trusted_omemo_keys">Huellas digitales OMEMO de confianza ciega</string> + <string name="not_trusted">No confiables</string> + <string name="invalid_barcode">Código QR inválido</string> </resources> diff --git a/src/main/res/values-eu/strings.xml b/src/main/res/values-eu/strings.xml index 3ebfd517..be7a915e 100644 --- a/src/main/res/values-eu/strings.xml +++ b/src/main/res/values-eu/strings.xml @@ -122,14 +122,12 @@ <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-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> <string name="error">Akats bat gertatu da</string> <string name="pref_grant_presence_updates">Presentzia eguneraketak eman</string> - <string name="pref_grant_presence_updates_summary">Prebentiboki presentzia eguneraketak eman eta eskatu sortu dituzun kontaktuetarako</string> <string name="subscriptions">Harpidetzak</string> <string name="your_account">Zure kontua</string> <string name="keys">Gakoak</string> @@ -247,6 +245,7 @@ <string name="contact_already_exists">Kontaktua existitzen da dagoeneko</string> <string name="join">Batu</string> <string name="conference_address">Konferentziaren helbidea</string> + <string name="conference_address_example">gela@konferentzia.adibidea.eus/ezizena</string> <string name="save_as_bookmark">Gorde laster-marka bezala</string> <string name="delete_bookmark">Laster-marka ezabatu</string> <string name="bookmark_already_exists">Laster-marka hau existitzen da dagoeneko</string> @@ -282,6 +281,7 @@ <string name="conference_requires_password">Konferentziak pasahitza behar du</string> <string name="enter_password">Sartu pasahitza</string> <string name="missing_presence_updates">Kontaktuaren presentzia eguneraketak falta dira</string> + <string name="missing_presence_subscription">Presentzia harpidetza falta da</string> <string name="request_presence_updates">Mesedez eskatu lehenago zure kontaktuaren presentzia eguneraketak.\n\n<small>Kontaktuak erabiltzen ari den bezeroa(k) zehazteko erabilika da hau.</small></string> <string name="request_now">Eskatu orain</string> <string name="delete_fingerprint">Hatz-marka ezabatu</string> @@ -329,6 +329,7 @@ <string name="check_x_filesize_on_host">Egiaztatu %1$sren neurria %2$s ostalarian</string> <string name="message_options">Mezuaren aukerak</string> <string name="copy_text">Testua kopiatu</string> + <string name="select_text">Testua hautatu</string> <string name="copy_original_url">Jatorrizko URLa kopiatu</string> <string name="send_again">Berriro bidali</string> <string name="file_url">Fitxategiaren URLa</string> @@ -336,8 +337,6 @@ <string name="url_copied_to_clipboard">URLa arbelera kopiatu da</string> <string name="message_copied_to_clipboard">Mezua arbelera kopiatu da</string> <string name="image_transmission_failed">Irudiaren transmisioak huts egin du</string> - <string name="scan_qr_code">QR kodea eskaneatu</string> - <string name="show_qr_code">QR kodea erakutsi</string> <string name="show_block_list">Blokeatutakoen zerrenda ikusi</string> <string name="account_details">Kontuaren xehetasunak</string> <string name="verify_otr">OTR egiaztatu</string> @@ -377,7 +376,6 @@ <string name="no_application_found_to_open_file">Fitxategia ireki dezakeen aplikaziorik ez da aurkitu</string> <string name="could_not_verify_fingerprint">Hatz-marka ezin izan da egiaztatu</string> <string name="manually_verify">Eskuz egiaztatu</string> - <string name="are_you_sure_verify_fingerprint">Ziur al zaude zure kontaktuaren OTR hatz-marka egiaztatu nahi duzulaz?</string> <string name="pref_show_dynamic_tags">Etiketa dinamikoak erakutsi</string> <string name="pref_show_dynamic_tags_summary">Irakurtzeko soilik diren etiketak erakutsi kontaktuen azpian</string> <string name="enable_notifications">Jakinarazpenak gaitu</string> @@ -470,7 +468,6 @@ <string name="contact_is_typing">%s idazten ari da...</string> <string name="contact_has_stopped_typing">%s(e)k idazteari utzi dio</string> <string name="pref_chat_states">Idazketa jakinarazpenak</string> - <string name="pref_chat_states_summary">Zure kontaktuak mezu berri bat noiz idazten ari zaren jakin dezan baimendu</string> <string name="send_location">Kokapena partekatu</string> <string name="show_location">Kokapena erakutsi</string> <string name="no_application_found_to_display_location">Kokapena erakutsi dezakeen aplikaziorik ez da aurkitu</string> @@ -650,6 +647,17 @@ <string name="unable_to_update_account">Ezin izan da kontua eguneratu</string> <string name="missing_presence_subscription_with_x">%s(r)ekin presentzia harpidetza falta da</string> <string name="missing_keys_from_x">%s(r)en OMEMO gakoak falta dira.</string> + <string name="missing_omemo_keys">OMEMO gakoak falta dira</string> <string name="wrong_conference_configuration">Hau ez da konferentzia pribatu, ez anonimo bat.</string> <string name="this_conference_has_no_members">Ez dago kiderik konferentzia honetan.</string> + <string name="report_jid_as_spammer">JIDa nahi gabeko mezuen bidaltzaile gisa salatu</string> + <string name="pref_delete_omemo_identities">OMEMO nortasunak ezabatu</string> + <string name="pref_delete_omemo_identities_summary">Zure OMEMO gakoak birsortu. Zure kontaktu guztiak zu egiaztatu beharko zaituzte berriz. Soilik azken aukera bezala erabili hau.</string> + <string name="delete_selected_keys">Hautatutako gakoak ezabatu</string> + <string name="error_publish_avatar_offline">Konektatuta egon behar zara zure profileko argazkia argitaratzeko.</string> + <string name="show_error_message">Akatsaren mezua erakutsi</string> + <string name="error_message">Akatsaren mezua</string> + <string name="data_saver_enabled">Datuen aurreztailea gaituta</string> + <string name="data_saver_enabled_explained">Zure sistema eragileak Conversations atzeko planoan dagoenean Internetera konekta dadin mugatzen ari da. Mezu berrien jakinarazpenak jasotzeko Conversationsi mugagabeko sarrera eman beharko zenioke Datuen aurreztailea piztuta dagoenean.\nConversationsek datuak aurrezteko esfortzua egiten jarraituko du ahal duenean.</string> + <string name="device_does_not_support_data_saver">Zure gailuak ez du Datuen aurreztailea ezgaitzea baimentzen Conversationserako</string> </resources> diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 63576e0e..290125dc 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -122,14 +122,14 @@ <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 le contact lorsque vous avez reçu et lu un message.</string> + <string name="pref_confirm_messages_summary">Faites savoir à vos contacts quand vous avez reçu et lu leurs messages</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">Autoriser les màj de disponibilité</string> - <string name="pref_grant_presence_updates_summary">Autoriser et demander par avance les mises à jour de disponibilité des contacts ajoutés.</string> + <string name="pref_grant_presence_updates_summary">Accordez et demandez par avance les abonnements de présence aux contacts que vous avez ajoutés</string> <string name="subscriptions">Publications</string> <string name="your_account">Votre compte</string> <string name="keys">Clefs</string> @@ -283,6 +283,7 @@ <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">Mises à jour de disponibilité manquantes pour ce contact</string> + <string name="missing_presence_subscription">La notification de présence est manquante </string> <string name="request_presence_updates">Veuillez demander à votre contact de partager ses mises à jour de disponibilité.\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> @@ -330,6 +331,7 @@ <string name="check_x_filesize_on_host">Vérification de la taille de %1$s sur %2$s</string> <string name="message_options">Options du message</string> <string name="copy_text">Copier le texte</string> + <string name="select_text">Sélectionnez 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> @@ -337,8 +339,8 @@ <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">Échec lors de l\'envoi de l\'image</string> - <string name="scan_qr_code">Scanner un code QR</string> - <string name="show_qr_code">Afficher le code QR</string> + <string name="scan_qr_code">Scanner le Code-barres 2D</string> + <string name="show_qr_code">Montrer le Code-barres 2D</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> @@ -378,7 +380,7 @@ <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">Êtes-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 votre contact?</string> <string name="pref_show_dynamic_tags">Afficher les tags dynamiques</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> @@ -471,7 +473,7 @@ <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">Informer votre contact lorsque vous êtes en train d\'écrire un message.</string> + <string name="pref_chat_states_summary">Faites savoir à vos contacts quand vous leur écrivez des 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> @@ -651,10 +653,33 @@ <string name="unable_to_update_account">Mise à jour du compte impossible</string> <string name="missing_presence_subscription_with_x">La notification de présence est manquante avec %s</string> <string name="missing_keys_from_x">La clef OMEMO de %s est manquante</string> + <string name="missing_omemo_keys">Clefs OMEMO manquantes</string> <string name="wrong_conference_configuration">La conférence n\'est ni privée, ni anonyme</string> <string name="this_conference_has_no_members">Aucun membre dans cette conférence</string> <string name="report_jid_as_spammer">Signaler ce JID comme envoyant des messages non sollicités</string> <string name="pref_delete_omemo_identities">Effacer les identités OMEMO</string> <string name="pref_delete_omemo_identities_summary">Régénérer vos clef OMEMO. Tous vos contacts devront les vérifier à nouveau. A n\'utiliser qu\'en dernier recours.</string> <string name="delete_selected_keys">Supprimer les clefs sélectionnées</string> + <string name="error_publish_avatar_offline">Vous devez être connecté pour publier votre avatar</string> + <string name="show_error_message">Afficher le message d\'erreur</string> + <string name="error_message">Message d\'erreur</string> + <string name="data_saver_enabled">Économie de données activée</string> + <string name="data_saver_enabled_explained">Votre système d\'exploitation empêche Conversations d’accéder à Internet en arrière plan. Pour recevoir les notifications de nouveaux messages, vous devez autoriser, quand le mode d\'économie de données est actif, à ce que Conversations ai un plein accès au réseau.\n Conversations essaiera autant que possible d\'économiser la data.</string> + <string name="device_does_not_support_data_saver">Votre appareil ne prend pas en charge la désactivation de l\'économiseur de données pour Conversations.</string> + <string name="error_unable_to_create_temporary_file">Impossible de créer un fichier temporaire</string> + <string name="this_device_has_been_verified">Cet appareil a été vérifié</string> + <string name="copy_fingerprint">Copier l\'empreinte</string> + <string name="all_omemo_keys_have_been_verified">Toutes les clés OMEMO ont étés vérifiées</string> + <string name="barcode_does_not_contain_fingerprints_for_this_conversation">Le Code-barres ne contient pas d\'empreintes pour cette conversation.</string> + <string name="verified_fingerprints">Empreintes vérifiées</string> + <string name="use_camera_icon_to_scan_barcode">Utilisez l\'appareil photo pour scanner le code-barres d\'un contact</string> + <string name="please_wait_for_keys_to_be_fetched">Veuillez attendre que les clés soient récupérées</string> + <string name="share_as_barcode">Partager par Code-barres</string> + <string name="share_as_uri">Partager par URI XMPP</string> + <string name="share_as_http">Partager par lien HTTP</string> + <string name="pref_blind_trust_before_verification">Faire Aveuglement Confiance Avant La Vérification</string> + <string name="pref_blind_trust_before_verification_summary">Faire automatiquement confiance à tous les nouveaux appareils des contacts qui n\'ont pas étés vérifiés avant et demandez une confirmation manuelle à chaque fois qu\'un contact vérifié ajoute un nouveau appareil.</string> + <string name="blindly_trusted_omemo_keys">Faire aveuglement confiance aux clés OMEMO</string> + <string name="not_trusted">Non approuvée</string> + <string name="invalid_barcode">Code-barres 2D invalide</string> </resources> diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index ea60f29c..82507c7b 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -67,7 +67,6 @@ <string name="accept">Aceptar</string> <string name="error">Produciuse un erro</string> <string name="pref_grant_presence_updates">Suscripción de presencia</string> - <string name="pref_grant_presence_updates_summary">Por defecto otorgar e pedir suscripcións de presencia dos contactos que creaches</string> <string name="subscriptions">Suscripcións</string> <string name="your_account">A túa conta</string> <string name="keys">Chaves</string> diff --git a/src/main/res/values-id/strings.xml b/src/main/res/values-id/strings.xml index 6ba858c7..78c17a2a 100644 --- a/src/main/res/values-id/strings.xml +++ b/src/main/res/values-id/strings.xml @@ -113,14 +113,12 @@ <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">UI</string> <string name="openpgp_error">OpenKeychain melaporkan kesalahan</string> <string name="error_decrypting_file">I/O Error menerjemahkan berkas</string> <string name="accept">Terima</string> <string name="error">Sebuah kesalahan terjadi</string> <string name="pref_grant_presence_updates">Memberikan perubahan kehadiran</string> - <string name="pref_grant_presence_updates_summary">Terlebih dahulu meminta dan berlangganan kehadiran untuk kontak Anda buat</string> <string name="subscriptions">Langganan</string> <string name="your_account">Akun anda</string> <string name="keys">Kunci</string> @@ -298,8 +296,6 @@ <string name="url_copied_to_clipboard">URL disalin ke clipboard</string> <string name="message_copied_to_clipboard">Pesan disalin ke clipboard</string> <string name="image_transmission_failed">pengiriman gambar gagal</string> - <string name="scan_qr_code">Pindai kode QR</string> - <string name="show_qr_code">Tampilkan kode QR</string> <string name="show_block_list">Tampilkan daftar blokir</string> <string name="account_details">Detil akun</string> <string name="verify_otr">Verifikasi OTR</string> @@ -336,7 +332,6 @@ <string name="no_application_found_to_open_file">Tidak ditemukan aplikasi untuk membuka berkas</string> <string name="could_not_verify_fingerprint">Tidak dapat verifikasi fingerprint</string> <string name="manually_verify">Verifikasi secara manual</string> - <string name="are_you_sure_verify_fingerprint">Yakin untuk memferifikasi OTR fingerprint kontak Anda?</string> <string name="pref_show_dynamic_tags">Tampilkan tag dinamis</string> <string name="pref_show_dynamic_tags_summary">Tampilan read-only tag di bawah kontak</string> <string name="enable_notifications">Aktifkan notifikasi</string> @@ -418,7 +413,6 @@ <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> <string name="send_location">Kirim lokasi</string> <string name="show_location">Tampilkan lokasi</string> <string name="no_application_found_to_display_location">Tidak ada aplikasi ditemukan untuk menampilkan lokasi</string> diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index d409b53f..e462e92a 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -122,14 +122,12 @@ <string name="pref_never_send_crash">Non inviare mai segnalazioni di errore</string> <string name="pref_never_send_crash_summary">Se scegli di inviare una segnalazione dell’errore aiuterai lo sviluppo di Conversations</string> <string name="pref_confirm_messages">Conferma Messaggi</string> - <string name="pref_confirm_messages_summary">Fai sapere ai tuoi contatti quando hai ricevuto il messaggio e l’hai letto</string> <string name="pref_ui_options">Interfaccia Utente</string> <string name="openpgp_error">OpenKeychain ha riportato un errore</string> <string name="error_decrypting_file">Errore di I/O nel decifrare il file</string> <string name="accept">Accetta</string> <string name="error">Si è verificato un errore</string> <string name="pref_grant_presence_updates">Concedi aggiornamenti della presenza</string> - <string name="pref_grant_presence_updates_summary">Concedi e chiedi preventivamente la sottoscrizione della presenza ai contatti che hai creato</string> <string name="subscriptions">Sottoscrizioni</string> <string name="your_account">Il tuo utente</string> <string name="keys">Chiavi</string> @@ -331,6 +329,7 @@ <string name="check_x_filesize_on_host">Controlla %1$s dimensione su %2$s</string> <string name="message_options">Opzioni del messaggio</string> <string name="copy_text">Copia testo</string> + <string name="select_text">Seleziona 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> @@ -338,8 +337,6 @@ <string name="url_copied_to_clipboard">URL copiato</string> <string name="message_copied_to_clipboard">Messaggio copiato</string> <string name="image_transmission_failed">Trasmissione dell’immagine fallita</string> - <string name="scan_qr_code">Scansiona codice QR</string> - <string name="show_qr_code">Mostra codice QR</string> <string name="show_block_list">Mostra la black list</string> <string name="account_details">Dettagli utente</string> <string name="verify_otr">Verifica OTR</string> @@ -379,7 +376,6 @@ <string name="no_application_found_to_open_file">Nessuna applicazione trovata per aprire il file</string> <string name="could_not_verify_fingerprint">Impronta non verificata</string> <string name="manually_verify">Verifica manuale</string> - <string name="are_you_sure_verify_fingerprint">Sei sicuro di voler verificare l’impronta OTR del contatto?</string> <string name="pref_show_dynamic_tags">Mostra tag dinamici</string> <string name="pref_show_dynamic_tags_summary">Mostra tag in sola lettura sotto i contatti</string> <string name="enable_notifications">Abilita le notifiche</string> @@ -472,7 +468,6 @@ <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> <string name="no_application_found_to_display_location">Non è stata trovata alcuna applicazione per mostrare la posizione</string> @@ -660,4 +655,9 @@ <string name="pref_delete_omemo_identities_summary">Rigenera le tue chiavi OMEMO. I tuoi contatti dovranno verificare un\'altra volta la tua identità. Solo come ultima possibilità.</string> <string name="delete_selected_keys">Cancella le chiavi selezionate</string> <string name="error_publish_avatar_offline">Devi essere connesso per pubblicare l\'avatar.</string> + <string name="show_error_message">Mostra messaggio di errore</string> + <string name="error_message">Messaggio di errore</string> + <string name="data_saver_enabled">Risparmio dati attivato</string> + <string name="data_saver_enabled_explained">Il tuo sistema operativo sta limitando l\'accesso internet a Conversations quando è in background. Per ricevere le notifiche di nuovi messaggi dovresti consentire l\'accesso senza limiti a Conversations quando il Risparmio dati è attivo.\nConversations cercherà comunque di risparmiare dati quando possibile.</string> + <string name="device_does_not_support_data_saver">Il tuo dispositivo non supporta la disattivazione del Risparmio dati per Conversations.</string> </resources> diff --git a/src/main/res/values-iw/strings.xml b/src/main/res/values-iw/strings.xml index 8ed4739f..a02b3c23 100644 --- a/src/main/res/values-iw/strings.xml +++ b/src/main/res/values-iw/strings.xml @@ -104,13 +104,11 @@ <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="openpgp_error">אפליקציית OpenKeychain דיווחה על שגיאה</string> <string name="error_decrypting_file">שגיאת I/O פענוח קובץ</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="subscriptions">הרשמות</string> <string name="your_account">החשבון שלך</string> <string name="keys">מפתחות</string> @@ -291,8 +289,6 @@ <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> @@ -328,7 +324,6 @@ <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> @@ -417,7 +412,6 @@ <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> diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 3e55d17a..1aa424f6 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -122,7 +122,7 @@ <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_confirm_messages_summary">あなたがメッセージを受信して読んだときに、連絡先に知らせます</string> <string name="pref_ui_options">UI</string> <string name="openpgp_error">OpenKeychain がエラーを報告しました</string> <string name="error_decrypting_file">ファイルの復号化中に I/O エラー</string> @@ -331,6 +331,7 @@ <string name="check_x_filesize_on_host">%2$s で %1$s のサイズを確認</string> <string name="message_options">メッセージオプション</string> <string name="copy_text">テキストをコピー</string> + <string name="select_text">テキストを選択</string> <string name="copy_original_url">元の URL をコピー</string> <string name="send_again">再送</string> <string name="file_url">ファイル URL</string> @@ -338,8 +339,8 @@ <string name="url_copied_to_clipboard">URL をクリップボードにコピーしました</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="scan_qr_code">2D バーコードをスキャン</string> + <string name="show_qr_code">2D バーコードを表示</string> <string name="show_block_list">ブロックリストを表示</string> <string name="account_details">アカウントの詳細</string> <string name="verify_otr">OTR を検証</string> @@ -472,7 +473,7 @@ <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="pref_chat_states_summary">あなたがメッセージを書いているときに、連絡先に知らせます</string> <string name="send_location">位置を送信</string> <string name="show_location">位置を表示</string> <string name="no_application_found_to_display_location">位置を表示するアプリケーションが見つかりません</string> @@ -657,4 +658,25 @@ <string name="pref_delete_omemo_identities_summary">OMEMO 鍵を再生成します。すべての連絡先を再度確認する必要があります。最後の手段としてのみ使用してください。</string> <string name="delete_selected_keys">選択した鍵を削除</string> <string name="error_publish_avatar_offline">アバターを公開するには接続する必要があります。</string> + <string name="show_error_message">エラーメッセージを表示</string> + <string name="error_message">エラーメッセージ</string> + <string name="data_saver_enabled">データセーバーを有効にしました</string> + <string name="data_saver_enabled_explained">お使いのオペレーティングシステムは、Conversationsがバックグラウンドのときにインターネットにアクセスすることを制限しています。新しいメッセージの通知を受信するには、データセーバーがオンになっているとき、Conversationsに無制限のアクセスを許可する必要があります。\nConversationsは可能なときにデータを保存するための努力をします。</string> + <string name="device_does_not_support_data_saver">お使いのデバイスは、Conversationsのデータセーバーを無効にすることはできません。</string> + <string name="error_unable_to_create_temporary_file">一時ファイルを作成できません</string> + <string name="this_device_has_been_verified">このデバイスは検証済です</string> + <string name="copy_fingerprint">フィンガープリントをコピー</string> + <string name="all_omemo_keys_have_been_verified">すべての OMEMO 鍵は検証済です</string> + <string name="barcode_does_not_contain_fingerprints_for_this_conversation">バーコードに、この会話のフィンガープリントが含まれていません。</string> + <string name="verified_fingerprints">フィンガープリントを検証しました</string> + <string name="use_camera_icon_to_scan_barcode">カメラを使用して連絡先のバーコードをスキャンします</string> + <string name="please_wait_for_keys_to_be_fetched">キーが取得されるのをお待ちください</string> + <string name="share_as_barcode">バーコードとして共有</string> + <string name="share_as_uri">XMPP URI として共有</string> + <string name="share_as_http">HTTP リンクとして共有</string> + <string name="pref_blind_trust_before_verification">検証前に白紙信託する</string> + <string name="pref_blind_trust_before_verification_summary">以前に確認されていない連絡先の新しいデバイスをすべて自動的に信頼し、確認済の連絡先が新しいデバイスを追加するたびに手動で確認するよう促します。</string> + <string name="blindly_trusted_omemo_keys">OMEMO 鍵を白紙信託しました</string> + <string name="not_trusted">信頼されていない</string> + <string name="invalid_barcode">不正な 2D バーコード</string> </resources> diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml index 1860cbc2..88202e68 100644 --- a/src/main/res/values-ko/strings.xml +++ b/src/main/res/values-ko/strings.xml @@ -71,12 +71,16 @@ <string name="send_failed">전송 실패 </string> <string name="send_rejected">거부됨 </string> <string name="preparing_image">이미지 전송 준비중 </string> + <string name="preparing_images">이미지 전송 준비중 </string> + <string name="sharing_files_please_wait">파일을 공유중입니다. 기다리세요...</string> <string name="action_clear_history">기록 삭제 </string> <string name="clear_conversation_history">대화 기록 삭제 </string> <string name="clear_histor_msg">이 대화의 모든 메세지를 삭제하시겠습니까? 경고: 이것은 다른 기기나 서버에 있는 메세지에는 영향을 미치지 않습니다. </string> <string name="delete_messages">메세지 삭제 </string> <string name="also_end_conversation">나중에 이 대화 끝내기 </string> + <string name="choose_presence">기기 선택</string> <string name="send_unencrypted_message">암호화하지 않은 메세지 전송</string> + <string name="send_message_to_x">%s 에게 매세지 보내기 </string> <string name="send_otr_message">OTR 암호화된 메세지 전송 </string> <string name="send_omemo_message">OMEMO로 암호화된 메세지</string> <string name="send_omemo_x509_message">v\\OMEMO로 암호화된 메세지 전송</string> @@ -100,19 +104,27 @@ <string name="pref_xmpp_resource_summary">이 클라이언트가 자신을 알아보는 이름</string> <string name="pref_accept_files">파일 수락 </string> <string name="pref_accept_files_summary">이 크기보다 작은 파일을 자동으로 수락 </string> + <string name="pref_attachments">첨부파일</string> + <string name="pref_return_to_previous">빠른 공유</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_led">LED 알림</string> + <string name="pref_led_summary">새 메세지 도착시 LED 깜빡이기</string> + <string name="pref_sound">알림음</string> + <string name="pref_sound_summary">새 메세지 도착시 알림음 재생</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">UI</string> <string name="openpgp_error">OpenKeychain이 오류를 보고합니다 </string> <string name="error_decrypting_file">파일 복호화 입출력 오류 </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="subscriptions">구독 </string> <string name="your_account">당신의 계정 </string> <string name="keys">키 </string> @@ -301,8 +313,6 @@ <string name="url_copied_to_clipboard">URL이 클립보드에 복사되었습니다 </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> @@ -339,7 +349,6 @@ <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> @@ -430,7 +439,6 @@ <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> diff --git a/src/main/res/values-nb-rNO/strings.xml b/src/main/res/values-nb-rNO/strings.xml index 55671015..b030242d 100644 --- a/src/main/res/values-nb-rNO/strings.xml +++ b/src/main/res/values-nb-rNO/strings.xml @@ -106,13 +106,11 @@ <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="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> @@ -301,8 +299,6 @@ <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> @@ -339,7 +335,6 @@ <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> @@ -430,7 +425,6 @@ <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> diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index 4d55e33d..2088d45e 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -122,14 +122,14 @@ <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_confirm_messages_summary">Laat je contacten weten wanneer je hun berichten ontvangen en gelezen hebt</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 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 voor aanwezigheidsabonnementen verlenen en vragen aan contacten die je hebt aangemaakt</string> + <string name="pref_grant_presence_updates_summary">Op voorhand toestemming voor aanwezigheidsupdates 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> @@ -331,6 +331,7 @@ <string name="check_x_filesize_on_host">Bestandsgrootte van %1$s op %2$s controleren</string> <string name="message_options">Berichtopties</string> <string name="copy_text">Tekst kopiëren</string> + <string name="select_text">Tekst selecteren</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> @@ -338,8 +339,8 @@ <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">QR-code scannen</string> - <string name="show_qr_code">QR-code tonen</string> + <string name="scan_qr_code">2D-barcode scannen</string> + <string name="show_qr_code">2D-barcode tonen</string> <string name="show_block_list">Geblokkeerde contacten weergeven</string> <string name="account_details">Accountgegevens</string> <string name="verify_otr">OTR bevestigen</string> @@ -379,7 +380,6 @@ <string name="no_application_found_to_open_file">Geen applicatie om bestand te openen</string> <string name="could_not_verify_fingerprint">Kon vingerafdruk niet bevestigen</string> <string name="manually_verify">Handmatig bevestigen</string> - <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 inschakelen</string> @@ -472,7 +472,6 @@ <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">\'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 om locatie weer te geven</string> @@ -660,4 +659,14 @@ <string name="pref_delete_omemo_identities_summary">Maak je OMEMO-sleutels opnieuw aan. Al je contacten zullen je opnieuw moeten verifiëren. Gebruik dit enkel als een laatste reddingsmiddel.</string> <string name="delete_selected_keys">Geselecteerde sleutels verwijderen</string> <string name="error_publish_avatar_offline">Je moet verbonden zijn om je avatar te kunnen publiceren.</string> + <string name="show_error_message">Toon foutbericht</string> + <string name="error_message">Foutbericht</string> + <string name="data_saver_enabled">Gegevensbesparing ingeschakeld</string> + <string name="data_saver_enabled_explained">Je besturingssysteem verhindert Conversations toegang tot het internet wanneer Conversations zich in de achtergrond bevindt. Om meldingen van nieuwe berichten te krijgen moet je Conversations onbeperkte toegang geven wanneer gegevensbesparing is ingeschakeld.\nConversations zal nog steeds proberen wanneer mogelijk gegevens te besparen.</string> + <string name="device_does_not_support_data_saver">Je apparaat ondersteunt het uitschakelen van gegevensbesparing voor Conversations niet.</string> + <string name="error_unable_to_create_temporary_file">Kan tijdelijk bestand niet aanmaken</string> + <string name="this_device_has_been_verified">Dit apparaat is geverifieerd</string> + <string name="copy_fingerprint">Vingerafdruk kopiëren</string> + <string name="all_omemo_keys_have_been_verified">Alle OMEMO-sleutels zijn geverifieerd</string> + <string name="verified_fingerprints">Geverifieerde vingerafdrukken</string> </resources> diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 7cbb7655..eeb43c6c 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -78,7 +78,9 @@ <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">po czym zakończ tę rozmowę</string> + <string name="choose_presence">Wybierz urządzenie</string> <string name="send_unencrypted_message">Wyślij wiadomość bez szyfrowania</string> + <string name="send_message_to_x">Wyślij wiadomość do %s</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> @@ -114,18 +116,17 @@ <string name="pref_led_summary">Migaj lampką powiadamiającą gdy nadejdzie wiadomość</string> <string name="pref_sound">Dzwonek</string> <string name="pref_sound_summary">Odtwórz dźwięk gdy nadejdzie wiadomość</string> + <string name="pref_notification_grace_period">Czas Bez Powiadomień</string> <string name="pref_advanced_options">Zaawansowane</string> <string name="pref_never_send_crash">Nie wysyłaj raportów awarii</string> <string name="pref_never_send_crash_summary">Wysyłając ślady stosu pomagasz rozwijać Conversations</string> <string name="pref_confirm_messages">Potwierdzenia wiadomości</string> - <string name="pref_confirm_messages_summary">Powiadamiaj kontakty o otrzymaniu lub przeczytaniu wiadomości</string> <string name="pref_ui_options">UI</string> <string name="openpgp_error">Wystąpił błąd OpenKeychain</string> <string name="error_decrypting_file">Błąd podczas deszyfrowania pliku</string> <string name="accept">Akceptuj</string> <string name="error">Wystąpił błąd</string> <string name="pref_grant_presence_updates">Zezwól na powiadomienia obecności</string> - <string name="pref_grant_presence_updates_summary">Automatycznie zezwalaj i pytaj o powiadomienia obecności, kiedy utworzysz kontakt</string> <string name="subscriptions">Subskrypcje</string> <string name="your_account">Twoje konto</string> <string name="keys">Klucze</string> @@ -238,6 +239,7 @@ <string name="contact_already_exists">Kontakt już istnieje</string> <string name="join">Dołącz</string> <string name="conference_address">Adres konferencji</string> + <string name="conference_address_example">konferencja@konferencje.example.com/nick</string> <string name="save_as_bookmark">Dodaj jako zakładkę</string> <string name="delete_bookmark">Usuń zakładkę</string> <string name="bookmark_already_exists">Zakładka już istnieje</string> @@ -272,6 +274,7 @@ <string name="conference_requires_password">Konferencja jest zabezpieczona hasłem</string> <string name="enter_password">Wprowadź hasło</string> <string name="missing_presence_updates">Kontakt nie udostępnia powiadomień o obecności</string> + <string name="missing_presence_subscription">Brakuje subskrypcji obecności</string> <string name="request_presence_updates">Poproś kontakt o udostępnienie powiadomień o obecności.\n\n<small>Pozwoli to na ustalenie klienta, z którego korzysta rozmówca.</small></string> <string name="request_now">Zażądaj teraz</string> <string name="delete_fingerprint">Usuń odcisk klucza</string> @@ -319,6 +322,7 @@ <string name="check_x_filesize_on_host">Sprawdź wielkość %1$s na %2$s</string> <string name="message_options">Opcje wiadomości</string> <string name="copy_text">Skopiuj tekst</string> + <string name="select_text">Zaznacz 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> @@ -326,8 +330,6 @@ <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> <string name="image_transmission_failed">Błąd podczas przesyłania obrazu</string> - <string name="scan_qr_code">Zeskanuj kod QR</string> - <string name="show_qr_code">Pokaż kod QR</string> <string name="show_block_list">Wyświetl listę banów</string> <string name="account_details">Szczegóły konta</string> <string name="verify_otr">Weryfikuj OTR</string> @@ -365,7 +367,6 @@ <string name="no_application_found_to_open_file">Nie odnaleziono aplikacji skojarzonej z typem pliku</string> <string name="could_not_verify_fingerprint">Weryfikacja odcisku klucza nieudana</string> <string name="manually_verify">Weryfikuj ręcznie</string> - <string name="are_you_sure_verify_fingerprint">Czy na pewno chcesz zweryfikować odcisk klucza OTR kontaktu?</string> <string name="pref_show_dynamic_tags">Etykiety kontaktów</string> <string name="pref_show_dynamic_tags_summary">Wyświetlaj etykiety pod kontaktami</string> <string name="enable_notifications">Włącz powiadomienia</string> @@ -457,7 +458,6 @@ <string name="contact_is_typing">%s pisze...</string> <string name="contact_has_stopped_typing">%s przestał(a) pisać</string> <string name="pref_chat_states">Powiadomienia pisania</string> - <string name="pref_chat_states_summary">Powiadamiaj rozmówcę, kiedy rozpoczynasz nową wiadomość</string> <string name="send_location">Wyślij lokalizację</string> <string name="show_location">Pokaż lokalizację</string> <string name="no_application_found_to_display_location">Nie odnaleziono aplikacji do wyświetlenia lokalizacji</string> @@ -599,4 +599,29 @@ <string name="secure_password_generated">Zostało wygenerowane bezpieczne hasło</string> <string name="device_does_not_support_battery_op">Twoje urządzenie nie pozwala na wyłączenie optymalizacji baterii</string> <string name="show_password">Pokaż hasło</string> + <string name="pref_delete_omemo_identities_summary">Wygeneruj jeszcze raz klucze OMEMO. Wszystkie Twoje kontakty będą musiały zweryfikować Twoje nowe klucze. Użyj tego tylko w ostateczności.</string> + <string name="delete_selected_keys">Usuń zaznaczone klucze</string> + <string name="error_publish_avatar_offline">Musisz być połączony/na, aby opublikować swój awatar.</string> + <string name="show_error_message">Pokaż komunikaty błędów</string> + <string name="error_message">Komunikat o Błędzie</string> + <string name="data_saver_enabled">Oszczędzanie danych jest włączone</string> + <string name="data_saver_enabled_explained">Twój system operacyjny blokuje dostęp do internetu Conversations, kiedy jest w tle. Aby dostawać powiadomienia dla nowych wiadomości powinieneś/powinnaś dać Conversations nieograniczony dostęp do internetu, kiedy Oszczędzanie danych jest włączone. +Conversations będzie wciąż ograniczał transfer danych, kiedy tylko to jest możliwe.</string> + <string name="device_does_not_support_data_saver">Twoje urządzenie nie wspiera wyłączenia Oszczędzania danych dla Conversations.</string> + <string name="error_unable_to_create_temporary_file">Niemożna utworzyć pliku tymczasowego</string> + <string name="this_device_has_been_verified">To urządzenie zostało zweryfikowane</string> + <string name="copy_fingerprint">Skopiuj odcisk</string> + <string name="all_omemo_keys_have_been_verified">Wszystkie odciski OMEMO zostały zweryfikowane</string> + <string name="barcode_does_not_contain_fingerprints_for_this_conversation">Kod kreskowy nie zawiera odcisków dla tej rozmowy.</string> + <string name="verified_fingerprints">Zaufane odciski</string> + <string name="use_camera_icon_to_scan_barcode">Użyj aparatu, aby zeskanować kod kreskowy kontaktu.</string> + <string name="please_wait_for_keys_to_be_fetched">Proszę czekać na ściągnięcie kluczy</string> + <string name="share_as_barcode">Udostępnij przez Kod Kreskowy</string> + <string name="share_as_uri">Udostępnij przez URI XMPP</string> + <string name="share_as_http">Udostępnij przez link HTTP</string> + <string name="pref_blind_trust_before_verification">Ślepo Ufaj Przed Weryfikacją</string> + <string name="pref_blind_trust_before_verification_summary">Automatycznie ufaj wszystkim nowym urządzeniom kontaktów, którzy nie zostali zweryfikowani wcześniej, i poproś o ręczne potwierdzenie za każdym, kiedy zweryfikowany kontakt dodaje nowe urządzenie.</string> + <string name="blindly_trusted_omemo_keys">Ślepo ufaj kluczom OMEMO</string> + <string name="not_trusted">Niezaufane</string> + <string name="invalid_barcode">Nieprawidłowy kod kreskowy 2D</string> </resources> diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index 4f66ba80..dd818d0a 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -121,21 +121,21 @@ <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">Permite que um contato saiba quando você recebeu e leu uma mensagem.</string> + <string name="pref_confirm_messages">Confirmação de mensagens</string> + <string name="pref_confirm_messages_summary">Permite que seus contatos saibam quando você recebeu e leu as mensagens deles.</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">Permite e solicita atualizações de presença aos contatos que você criar</string> + <string name="pref_grant_presence_updates">Fornecer atualizações de presença</string> + <string name="pref_grant_presence_updates_summary">Permite e solicita, antecipadamente, atualizações de presença aos contatos que você criou</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="ask_for_presence_updates">Solicitar 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> @@ -283,6 +283,7 @@ <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="missing_presence_subscription">Sem requisição de presença</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> @@ -330,6 +331,7 @@ <string name="check_x_filesize_on_host">Verifique o tamanho de %1$s em %2$s</string> <string name="message_options">Opções da mensagem</string> <string name="copy_text">Copiar o texto</string> + <string name="select_text">Selecionar 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> @@ -337,8 +339,8 @@ <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="scan_qr_code">Capturar código de barras 2D</string> + <string name="show_qr_code">Exibir código de barras 2D</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> @@ -378,7 +380,7 @@ <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="are_you_sure_verify_fingerprint">Tem certeza que deseja verificar as impressões digitais OTR do seu contato?</string> <string name="pref_show_dynamic_tags">Exibir etiquetas dinâmicas</string> <string name="pref_show_dynamic_tags_summary">Exibe etiquetas de somente-leitura abaixo dos contatos.</string> <string name="enable_notifications">Habilitar notificações</string> @@ -471,7 +473,7 @@ <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">Permite que seus contatos vejam quando você estiver digitando uma nova mensagem.</string> + <string name="pref_chat_states_summary">Permite aos seus usuários saberem quando você está escrevendo mensagens para eles</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> @@ -644,17 +646,35 @@ <string name="payment_required">É necessário efetuar pagamento</string> <string name="missing_internet_permission">Permissões de Internet ausentes</string> <string name="me">Eu</string> - <string name="contact_asks_for_presence_subscription">O contato está solicitando atualizações de presença</string> + <string name="contact_asks_for_presence_subscription">O contato está requisitando acompanhar sua presença</string> <string name="allow">Permitir</string> <string name="no_permission_to_access_x">Sem permissão para acessar %s</string> <string name="remote_server_not_found">O servidor remoto não foi encontrado</string> <string name="unable_to_update_account">Não foi possível atualizar a conta</string> - <string name="missing_presence_subscription_with_x">Sem permissão para atualizações de presença de %s.</string> + <string name="missing_presence_subscription_with_x">Sem requisição de atualizações de presença com %s.</string> <string name="missing_keys_from_x">Sem chaves OMEMO para %s.</string> + <string name="missing_omemo_keys">Chaves OMEMO ausentes</string> <string name="wrong_conference_configuration">Essa conferência não é privada nem anônima.</string> <string name="this_conference_has_no_members">Não existem ninguém nessa conferência.</string> <string name="report_jid_as_spammer">Informe que esse JID está enviando mensagens indesejáveis</string> <string name="pref_delete_omemo_identities">Excluir identidades OMEMO</string> <string name="pref_delete_omemo_identities_summary">Regerar suas chaves OMEMO. Todos os seus contatos terão que verificá-lo novamente. Utilize isso somente como último recurso.</string> <string name="delete_selected_keys">Excluir as chaves selecionadas</string> + <string name="error_publish_avatar_offline">É necessário estar conectado para publicar o avatar.</string> + <string name="show_error_message">Exibir a mensagem de erro</string> + <string name="error_message">Mensagem de erro</string> + <string name="data_saver_enabled">Economia de dados habilitada</string> + <string name="data_saver_enabled_explained">O seu sistema operacional está restringindo o acesso à internet em segundo plano pelo Conversations. Para receber notificações de novas mensagens você deve permitir que o Conversations tenha acesso irrestrito quando a economia de dados estiver ativada.\nO Conversations fará um esforço para economizar dados sempre que possível.</string> + <string name="device_does_not_support_data_saver">O seu dispositivo não permite que a economia de dados seja desativada para o Conversations.</string> + <string name="error_unable_to_create_temporary_file">Não foi possível criar o arquivo temporário</string> + <string name="this_device_has_been_verified">Este dispositivo foi verificado</string> + <string name="copy_fingerprint">Copiar impressão digital</string> + <string name="all_omemo_keys_have_been_verified">Todas as chaves OMEMO foram verificadas</string> + <string name="barcode_does_not_contain_fingerprints_for_this_conversation">O código de barras não contém impressões digitais para esta conversa.</string> + <string name="verified_fingerprints">Impressões digitais verificadas</string> + <string name="use_camera_icon_to_scan_barcode">Use a câmera para capturar o código de barras de um contato</string> + <string name="share_as_barcode">Compartilhar como código de barras</string> + <string name="share_as_uri">Compartilhar como uma URI XMPP</string> + <string name="share_as_http">Compartilhar como um link HTTP</string> + <string name="not_trusted">Não confiável</string> </resources> diff --git a/src/main/res/values-pt/strings.xml b/src/main/res/values-pt/strings.xml index 9d5bd169..09ab0747 100644 --- a/src/main/res/values-pt/strings.xml +++ b/src/main/res/values-pt/strings.xml @@ -78,7 +78,9 @@ <string name="clear_histor_msg">Deseja remover todas as mensagens nesta conversa?\n\n<b>Aviso:<b> Isto não irá influenciar mensagens guardadas noutros dispositivos ou servidores.</string> <string name="delete_messages">Remover mensagens</string> <string name="also_end_conversation">Terminar esta conversa depois</string> + <string name="choose_presence">Escolher dispositivo</string> <string name="send_unencrypted_message">Enviar mensagem não cifrada</string> + <string name="send_message_to_x">Enviar mensagem para %s</string> <string name="send_otr_message">Enviar mensagem cifrada com OTR</string> <string name="send_omemo_message">Enviar mensagem cifrada com OMEMO</string> <string name="send_omemo_x509_message">Enviar mensagem cifrada com v\\OMEMO</string> @@ -118,14 +120,12 @@ <string name="pref_never_send_crash">Nunca enviar relatórios de falhas</string> <string name="pref_never_send_crash_summary">Ao enviar os stack traces você está a ajudar ao desenvolvimento contínuo de Conversations</string> <string name="pref_confirm_messages">Confirmar mensagens</string> - <string name="pref_confirm_messages_summary">Permitir que um contacto saiba quando você recebeu e leu uma mensagem</string> <string name="pref_ui_options">Interface do Utilizador</string> <string name="openpgp_error">O OpenKeychain reportou um erro</string> <string name="error_decrypting_file">Erro de I/O ao decifrar o ficheiro</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">Conceder ou pedir subscrição de presença antecipadamente para contactos que tenha criado.</string> <string name="subscriptions">Subscrições</string> <string name="your_account">A sua conta</string> <string name="keys">Chaves</string> @@ -318,6 +318,7 @@ <string name="check_x_filesize_on_host">Verifique o tamanho de %1$s em %2$s</string> <string name="message_options">Opções de mensagem</string> <string name="copy_text">Copiar texto</string> + <string name="select_text">Selecionar texto</string> <string name="copy_original_url">Copiar o URL original</string> <string name="send_again">Enviar novamente</string> <string name="file_url">URL do ficheiro</string> @@ -325,8 +326,6 @@ <string name="url_copied_to_clipboard">URL copiado para a área de transferência</string> <string name="message_copied_to_clipboard">Mensagem copiada para a área de transferência</string> <string name="image_transmission_failed">A transmissão da imagem falhou</string> - <string name="scan_qr_code">Ler código QR</string> - <string name="show_qr_code">Mostrar código QR</string> <string name="show_block_list">Mostar lista de bloqueios</string> <string name="account_details">Detalhes da conta</string> <string name="verify_otr">Verificar OTR</string> @@ -348,6 +347,7 @@ <string name="conversations_foreground_service">Conversations</string> <string name="pref_keep_foreground_service">Manter o serviço em primeiro plano</string> <string name="pref_keep_foreground_service_summary">Previne o sistema operativo de terminar a sua conexão</string> + <string name="pref_export_logs">Exportar histórico</string> <string name="notification_export_logs_title">A escrever os logs para o cartão SD</string> <string name="choose_file">Escolher ficheiro</string> <string name="receiving_x_file">A receber %1$s (%2$d%% concluído)</string> @@ -364,7 +364,6 @@ <string name="no_application_found_to_open_file">Não foi encontrada nenhuma aplicação para abrir o ficheiro</string> <string name="could_not_verify_fingerprint">Não foi possível verificar a impressão digital</string> <string name="manually_verify">Verificar manualmente</string> - <string name="are_you_sure_verify_fingerprint">Tem a certeza que quer verificar a impressão digital OTR do seu contacto?</string> <string name="pref_show_dynamic_tags">Mostrar tags dinâmicas</string> <string name="pref_show_dynamic_tags_summary">Mostrar tags read-only debaixo dos contactos</string> <string name="enable_notifications">Ativar notificações</string> @@ -456,7 +455,6 @@ <string name="contact_is_typing">%s está a escrever...</string> <string name="contact_has_stopped_typing">%s parou de escrever</string> <string name="pref_chat_states">Notificações de escrita</string> - <string name="pref_chat_states_summary">Permitir que um contacto saiba quando está a escrever 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 encontrada nenhuma aplicação para mostrar a localização</string> @@ -576,4 +574,5 @@ <string name="security_error_invalid_file_access">Erro de segurança: Acesso ao ficheiro inválido</string> <string name="no_application_to_share_uri">Não foi encontrada nenhuma aplicação para partilhar o URI</string> <string name="share_uri_with">Partilhar URI com...</string> + <string name="create_account">Criar conta</string> </resources> diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index 909b14d2..5590cd29 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -3,7 +3,7 @@ <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 conversatia</string> + <string name="action_end_conversation">Inchide 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> @@ -20,7 +20,7 @@ <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 Conversations</string> - <string name="title_activity_start_conversation">Porneste conversatie</string> + <string name="title_activity_start_conversation">Porneste o 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> @@ -46,7 +46,7 @@ <string name="register_account">Inregistreaza un cont nou pe server</string> <string name="change_password_on_server">Schimba parola pe server</string> <string name="share_with">Partajeaza cu...</string> - <string name="start_conversation">Porneste conversatie</string> + <string name="start_conversation">Porneste o conversatie</string> <string name="invite_contact">Invita contact</string> <string name="contacts">Contacte</string> <string name="cancel">Anuleaza</string> @@ -103,15 +103,15 @@ <string name="pref_xmpp_resource">Nume client XMPP</string> <string name="pref_xmpp_resource_summary">Identificatorul acestui client</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_accept_files_summary">Mai mici decat...</string> <string name="pref_attachments">Atasamente</string> <string name="pref_return_to_previous">Partajare rapida</string> - <string name="pref_return_to_previous_summary">Intoarcere la activitatea precedenta in loc sa deschizi conversatia, dupa ce ai partajat ceva</string> + <string name="pref_return_to_previous_summary">Dupa ce ai partajat ceva, intoarce-te la activitatea precedenta in loc sa deschizi conversatia</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_notifications_summary">Atunci 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_vibrate_summary">Atunci cand un nou mesaj este primit</string> <string name="pref_led">Notificare LED</string> <string name="pref_led_summary">Clipeste lumina de notificare atunci cand un nou mesaj este primit</string> <string name="pref_sound">Ton de apel</string> @@ -122,7 +122,7 @@ <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_confirm_messages_summary">Contactul este notificat atunci 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> @@ -294,7 +294,7 @@ <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_allow_message_correction_summary">Contactele pot 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> @@ -306,15 +306,15 @@ <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">Mareste fontul</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_indicate_received_summary">Mesajele primite vor fi marcate cu o bifa 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_conference_name_summary">Foloseste subiectul camerei 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> @@ -331,6 +331,7 @@ <string name="check_x_filesize_on_host">Verifica marimea %1$s pe %2$s</string> <string name="message_options">Optiuni mesaje</string> <string name="copy_text">Copiaza text</string> + <string name="select_text">Selecteaza 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> @@ -338,8 +339,8 @@ <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="scan_qr_code">Scaneaza cod de bare 2D</string> + <string name="show_qr_code">Arata cod de bare 2D</string> <string name="show_block_list">Arata lista blocata</string> <string name="account_details">Detalii cont</string> <string name="verify_otr">Verifica OTR</string> @@ -380,8 +381,8 @@ <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="pref_show_dynamic_tags">Arata etichetele de stare</string> + <string name="pref_show_dynamic_tags_summary">Sub contacte arata etichete dinamice</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> @@ -638,7 +639,7 @@ Emitent</string> <string name="pref_theme_light">Tema luminoasa</string> <string name="pref_theme_dark">Tema intunecata</string> <string name="pref_use_green_background">Fundal verde</string> - <string name="pref_use_green_background_summary">Foloseste la mesajele primite un fundal verde</string> + <string name="pref_use_green_background_summary">Pentru mesajele primite</string> <string name="unable_to_connect_to_keychain">Nu s-a putut contacta OpenKeychain</string> <string name="this_device_is_no_longer_in_use">Acest dispozitiv nu mai este in uz</string> <string name="type_pc">PC</string> @@ -659,9 +660,30 @@ Emitent</string> <string name="missing_omemo_keys">Chei OMEMO lipsa</string> <string name="wrong_conference_configuration">Aceasta este o conferinta care nu este privata si nu este anonima.</string> <string name="this_conference_has_no_members">Conferinta nu are nici un membru.</string> - <string name="report_jid_as_spammer">Raporteaza ca aceasta identitate (JID) trimite mesaje nedorite.</string> + <string name="report_jid_as_spammer">Raporteaza ca aceasta identitate trimite mesaje nedorite.</string> <string name="pref_delete_omemo_identities">Sterge identitatile OMEMO</string> <string name="pref_delete_omemo_identities_summary">Regenereaza cheile personale OMEMO. Toate contactele vor fi obligate sa verifice cheile tale din nou. Foloseste asta ca o ultima optiune.</string> <string name="delete_selected_keys">Sterge cheile selectate</string> <string name="error_publish_avatar_offline">Pentru a putea publica avatarul trebuie sa existe o conexiune.</string> + <string name="show_error_message">Arata mesaj de eroare</string> + <string name="error_message">Mesaj de eroare</string> + <string name="data_saver_enabled">Economizorul de date este activat</string> + <string name="data_saver_enabled_explained">Sistemul de operare restrictioneaza accesul la Internet pentru Conversations atunci cand este in fundal. Pentru a primi in continuare notificari de mesaje noi trebuie sa acordati acces ne restrictionat pentru Conversations atunci cand Economizorul de date este activ.\nConversations totusi face eforturi sa economiseasca datele atunci cand este posibil.</string> + <string name="device_does_not_support_data_saver">Dispozitivul nu suporta dezactivarea Economizorului de date pentru Conversations.</string> + <string name="error_unable_to_create_temporary_file">Nu se poate crea un fisier temporar</string> + <string name="this_device_has_been_verified">Acest dispozitiv a fost verificat</string> + <string name="copy_fingerprint">Copiaza amprenta</string> + <string name="all_omemo_keys_have_been_verified">Toate cheile OMEMO au fost verificate</string> + <string name="barcode_does_not_contain_fingerprints_for_this_conversation">Codul de bare nu contine amprente pentru aceasta conversatie.</string> + <string name="verified_fingerprints">Amprente verificate</string> + <string name="use_camera_icon_to_scan_barcode">Foloseste camera pentru a scana codul de bare al contactului</string> + <string name="please_wait_for_keys_to_be_fetched">Asteptati cat se preiau cheile</string> + <string name="share_as_barcode">Partajeaza un cod de bare</string> + <string name="share_as_uri">Partajeaza ca adresa XMPP</string> + <string name="share_as_http">Partajeaza ca legatura HTTP</string> + <string name="pref_blind_trust_before_verification">Incredere Oarba Inainte de Verificare</string> + <string name="pref_blind_trust_before_verification_summary">Ai incredere in toate dispozitivele noi ale contactelor care nu au fost verificate anterior, si cere confirmare manuala de fiecare data cand un contact adauga un dispozitiv nou.</string> + <string name="blindly_trusted_omemo_keys">Incredere oarba in aceste chei OMEMO</string> + <string name="not_trusted">De neincredere</string> + <string name="invalid_barcode">Cod de bare 2D invalid</string> </resources> diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index ad62138e..812068ed 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -29,7 +29,7 @@ <string name="unread_conversations">сообщен. не прочитано</string> <string name="sending">отправка…</string> <string name="message_decrypting">Расшифровка сообщения. Подождите…</string> - <string name="pgp_message">OpenPGP зашифрованное сообщение</string> + <string name="pgp_message">OpenPGP зашифр. сообщение</string> <string name="nick_in_use">Имя уже используется</string> <string name="admin">Администратор</string> <string name="owner">Владелец</string> @@ -81,10 +81,10 @@ <string name="choose_presence">Выберите устройство</string> <string name="send_unencrypted_message">Нешифрованное сообщение</string> <string name="send_message_to_x">Сообщение для %s</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="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> @@ -122,14 +122,12 @@ <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> <string name="error">Произошла ошибка</string> <string name="pref_grant_presence_updates">Предоставлять обновления</string> - <string name="pref_grant_presence_updates_summary">Разрешить и запрашивать статус присутствия для созданных вами контактов</string> <string name="subscriptions">Подписки</string> <string name="your_account">Ваш аккаунт</string> <string name="keys">Ключи</string> @@ -270,7 +268,7 @@ <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="send_private_message_to">Приватное сообщение %s</string> <string name="connect">Подключиться</string> <string name="account_already_exists">Аккаунт уже существует</string> <string name="next">Далее</string> @@ -331,6 +329,7 @@ <string name="check_x_filesize_on_host">Проверить размер %1$s на %2$s</string> <string name="message_options">Опции сообщения</string> <string name="copy_text">Копировать текст</string> + <string name="select_text">Выбрать текст</string> <string name="copy_original_url">Копировать адрес ссылки</string> <string name="send_again">Отправить ещё раз</string> <string name="file_url">URL файла</string> @@ -338,8 +337,6 @@ <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> @@ -353,7 +350,7 @@ <string name="respond">Ответ</string> <string name="failed">Не удалось</string> <string name="secrets_do_not_match">Ключи не совпадают</string> - <string name="try_again">Попробуйте ещё раз</string> + <string name="try_again">Повторить</string> <string name="finish">Завершить</string> <string name="verified">Подтверждён!</string> <string name="smp_requested">Контакт запросил подтверждение SMP</string> @@ -379,7 +376,6 @@ <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> @@ -472,14 +468,13 @@ <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="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> @@ -666,4 +661,9 @@ <string name="pref_delete_omemo_identities_summary">Создать заново OMEMO ключи. Необходимо повторное подтверждение. Используйте только в крайнем случае.</string> <string name="delete_selected_keys">Удалить отмеченные</string> <string name="error_publish_avatar_offline">Вы должны подключиться для публикации аватара.</string> + <string name="show_error_message">Текст ошибки</string> + <string name="error_message">Текст ошибки</string> + <string name="this_device_has_been_verified">Это устройство было подтверждено</string> + <string name="copy_fingerprint">Копировать отпечаток</string> + <string name="all_omemo_keys_have_been_verified">Все OMEMO-ключи были подтверждены</string> </resources> diff --git a/src/main/res/values-sk/strings.xml b/src/main/res/values-sk/strings.xml index a1c3e89c..a57d3b9d 100644 --- a/src/main/res/values-sk/strings.xml +++ b/src/main/res/values-sk/strings.xml @@ -101,13 +101,11 @@ <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="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> <string name="error">Došlo k chybe</string> <string name="pref_grant_presence_updates">Povoliť aktualizáciu stavu</string> - <string name="pref_grant_presence_updates_summary">Aktívne povoliť a žiadať o zasielanie zmien stavu pre vytvorené kontakty</string> <string name="subscriptions">Odbery</string> <string name="your_account">Váš účet</string> <string name="keys">Kľúče</string> @@ -289,8 +287,6 @@ <string name="url_copied_to_clipboard">URL skopírovaný do schránky</string> <string name="message_copied_to_clipboard">Správa skopírovaná do schránky</string> <string name="image_transmission_failed">Zlyhal prenos obrázku</string> - <string name="scan_qr_code">Skenovať kód QR</string> - <string name="show_qr_code">Zobraziť kód QR</string> <string name="show_block_list">Zobraziť zoznam blokovaných</string> <string name="account_details">Detaily účtu</string> <string name="verify_otr">Overiť OTR</string> @@ -326,7 +322,6 @@ <string name="no_application_found_to_open_file">Nenašla sa žiadna aplikácia na otvorenie súboru</string> <string name="could_not_verify_fingerprint">Nepodarilo sa overiť identifikátor</string> <string name="manually_verify">Overiť manuálne</string> - <string name="are_you_sure_verify_fingerprint">Ste si istý, že chcete overiť OTR identifikátor vašich kontaktov? </string> <string name="pref_show_dynamic_tags">Zobraziť dynamické etikety</string> <string name="pref_show_dynamic_tags_summary">Zobraziť etikety na čítanie pod kontakty</string> <string name="enable_notifications">Povoliť upozornenia</string> @@ -405,7 +400,6 @@ <string name="disable_account">Vypnúť účet</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> <string name="send_location">Poslať polohu</string> <string name="show_location">Zobraziť polohu</string> <string name="no_application_found_to_display_location">Nenašla sa aplikácia na zobrazenie polohy</string> diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml index 46723f17..f8cb5622 100644 --- a/src/main/res/values-sr/strings.xml +++ b/src/main/res/values-sr/strings.xml @@ -62,8 +62,8 @@ <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="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> @@ -122,14 +122,12 @@ <string name="pref_never_send_crash">Никад не шаљи извештаје о паду</string> <string name="pref_never_send_crash_summary">Слањем контратрага помажете текући развој Конверзације</string> <string name="pref_confirm_messages">Потврди поруке</string> - <string name="pref_confirm_messages_summary">Обзнаните контакту кад примите и прочитате поруку</string> <string name="pref_ui_options">Сучеље</string> <string name="openpgp_error">Отворени кључарник је пријавио грешку</string> <string name="error_decrypting_file">У/И грешка дешифровања фајла</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="subscriptions">Претплате</string> <string name="your_account">Ваш налог</string> <string name="keys">Кључеви</string> @@ -331,6 +329,7 @@ <string name="check_x_filesize_on_host">Провери величину %1$s na %2$s</string> <string name="message_options">Опције поруке</string> <string name="copy_text">Копирај текст</string> + <string name="select_text">Изабери текст</string> <string name="copy_original_url">Копирај изворни УРЛ</string> <string name="send_again">Пошаљи поново</string> <string name="file_url">УРЛ фајла</string> @@ -338,8 +337,8 @@ <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">Очитај бар-кôд</string> - <string name="show_qr_code">Прикажи бар-кôд</string> + <string name="scan_qr_code">Очитај 2Д бар-кôд</string> + <string name="show_qr_code">Прикажи 2Д бар-кôд</string> <string name="show_block_list">Прикажи списак блокираних</string> <string name="account_details">Детаљи налога</string> <string name="verify_otr">Овери ОТР</string> @@ -379,7 +378,6 @@ <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">Желите ли заиста да оверите ОТР отисак вашег контакта?</string> <string name="pref_show_dynamic_tags">Прикажи динамичке ознаке</string> <string name="pref_show_dynamic_tags_summary">Приказ ознака испод контаката</string> <string name="enable_notifications">Укључи обавештења</string> @@ -472,7 +470,6 @@ <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> @@ -532,12 +529,12 @@ <string name="pref_show_connection_options_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="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="unable_to_fetch_mam_prefs">Не могу да добавим поставке архивисања</string> <string name="captcha_required">Потребна стопка</string> <string name="captcha_hint">Унесите текст са слике изнад</string> <string name="certificate_chain_is_not_trusted">Ланац сертификата није поуздан</string> @@ -599,6 +596,8 @@ <string name="security_error_invalid_file_access">Безбедносна грешка: неисправан приступ фајлу</string> <string name="no_application_to_share_uri">Нема апликације за дељење веза</string> <string name="share_uri_with">Подели везу помоћу…</string> + <string name="welcome_text">ИксМПП је протокол независан од провајдера. Можете да користите овог клијента са било којим ИксМПП сервером.\nИпак, због погодности омогућили смо једноставно прављење налога на conversations.im¹, сервер посебно прилагођен за коришћење са Конверзацијом.</string> + <string name="magic_create_text">Водићемо вас кроз процес прављења налога на conversations.im.¹\nАко изаберете conversations.im за даваоца услуге моћи ћете да комуницирате са корисницима других сервера ако им дате ваш Џабер ИД.</string> <string name="your_full_jid_will_be">Ваш пуни Џабер ИД ће бити: %s</string> <string name="create_account">Направи налог</string> <string name="use_own_provider">Користићу сопствени провајдер</string> @@ -636,7 +635,7 @@ <string name="pref_theme_dark">Тамна тема</string> <string name="pref_use_green_background">Зелена позадина</string> <string name="pref_use_green_background_summary">Зелена позадина за примљене поруке</string> - <string name="unable_to_connect_to_keychain">Не могох да се повежем са Отвореним кључарником</string> + <string name="unable_to_connect_to_keychain">Не могу да се повежем са Отвореним кључарником</string> <string name="this_device_is_no_longer_in_use">Овај уређај више није у употреби</string> <string name="type_pc">Рачунар</string> <string name="type_phone">Мобилни телефон</string> @@ -650,7 +649,7 @@ <string name="allow">Дозволи</string> <string name="no_permission_to_access_x">Нема дозвола за приступ %s</string> <string name="remote_server_not_found">Удаљени сервер није нађен</string> - <string name="unable_to_update_account">Не могох да се ажурирам налог</string> + <string name="unable_to_update_account">Не могу да ажурирам налог</string> <string name="missing_presence_subscription_with_x">Нема претплате на присутност са %s.</string> <string name="missing_keys_from_x">Нема ОМЕМО кључева у %s.</string> <string name="missing_omemo_keys">Нема ОМЕМО кључева</string> @@ -661,4 +660,13 @@ <string name="pref_delete_omemo_identities_summary">Поновно генерисање ОМЕМО кључева. Сви ваши контакти ће морати поново да вас овере. Користите ово само у крајњем случају.</string> <string name="delete_selected_keys">Обриши изабране кључеве</string> <string name="error_publish_avatar_offline">Морате бити повезани да бисте објавили ваш аватар.</string> + <string name="show_error_message">Прикажи поруку грешке</string> + <string name="error_message">Порука грешке</string> + <string name="error_unable_to_create_temporary_file">Не могу да направим привремени фајл</string> + <string name="this_device_has_been_verified">Овај уређај је оверен.</string> + <string name="copy_fingerprint">Копирај отисак</string> + <string name="all_omemo_keys_have_been_verified">Сви ОМЕМО кључеви су оверени</string> + <string name="share_as_barcode">Подели као бар-кôд</string> + <string name="share_as_uri">Подели као ИксМПП УРИ</string> + <string name="share_as_http">Подели као ХТТП везу</string> </resources> diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index 51cf173d..92288264 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -122,14 +122,14 @@ <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_confirm_messages_summary">Låt dina kontakter veta när du har mottagit och läst deras meddelanden</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> <string name="error">Ett fel har inträffat</string> <string name="pref_grant_presence_updates">Tillåt tillänglighetsuppdateringar</string> - <string name="pref_grant_presence_updates_summary">Tillåt i förväg och be om tillgänglighetsuppdateringar för kontakter du skapat</string> + <string name="pref_grant_presence_updates_summary">Tillåt och be om abonnemangsbegäran i förväg för kontakter du har skapat</string> <string name="subscriptions">Abonnemang</string> <string name="your_account">Ditt konto</string> <string name="keys">Nycklar</string> @@ -331,6 +331,7 @@ <string name="check_x_filesize_on_host">Kontrollera filstorlek för %1$s på %2$s</string> <string name="message_options">Meddelandealternativ</string> <string name="copy_text">Kopiera text</string> + <string name="select_text">Markera 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> @@ -338,8 +339,8 @@ <string name="url_copied_to_clipboard">URL kopierad till urklipp</string> <string name="message_copied_to_clipboard">Meddelande kopierat till urklipp</string> <string name="image_transmission_failed">Bildöverföring lyckades inte</string> - <string name="scan_qr_code">Skanna QR-kod</string> - <string name="show_qr_code">Visa QR-kod</string> + <string name="scan_qr_code">Scanna 2D-streckkod</string> + <string name="show_qr_code">Visa 2D-streckkod</string> <string name="show_block_list">Visa blockeringslista</string> <string name="account_details">Kontodetaljer</string> <string name="verify_otr">Verifiera OTR</string> @@ -379,7 +380,7 @@ <string name="no_application_found_to_open_file">Ingen applikation kunde hittas för att öppna filen</string> <string name="could_not_verify_fingerprint">Kunde inte verifiera fingeravtryck</string> <string name="manually_verify">Verifiera manuellt</string> - <string name="are_you_sure_verify_fingerprint">Är du säker på att du vill verifiera din kontakts OTR-fingeravtryck</string> + <string name="are_you_sure_verify_fingerprint">Är du säker på att du vill verifiera din kontakts OTR-fingeravtryck?</string> <string name="pref_show_dynamic_tags">Visa dynamiska taggar</string> <string name="pref_show_dynamic_tags_summary">Visa skrivskyddade taggar under kontakter</string> <string name="enable_notifications">Aktivera notifieringar</string> @@ -472,7 +473,7 @@ <string name="contact_is_typing">%s skriver...</string> <string name="contact_has_stopped_typing">%s har slutat skriva</string> <string name="pref_chat_states">Skriv-notifieringar</string> - <string name="pref_chat_states_summary">Låter dina kontakter veta när du skriver ett nytt meddelande</string> + <string name="pref_chat_states_summary">Låt dina kontakter veta när du skriver meddelande till dem</string> <string name="send_location">Skicka position</string> <string name="show_location">Visa position</string> <string name="no_application_found_to_display_location">Kunde inte hitta applikation för att visa position</string> @@ -596,6 +597,8 @@ <string name="security_error_invalid_file_access">Säkerhetsfel: Ogiltig filaccess</string> <string name="no_application_to_share_uri">Ingen applikation kunde hittas för att dela URI</string> <string name="share_uri_with">Dela URI med...</string> + <string name="welcome_text">XMPP är ett leverantörsoberoende protokoll. Du kan använda denna klient med vilken XMPP server du vill.\nFör din bekvämlighet har vi gjort det enkelt att skapa ett konto hos conversations.im¹; en leverantör speciellt anpassad för att användas med Conversations.</string> + <string name="magic_create_text">Du kommer nu att få hjälp med att skapa ett konto på conversations.im.¹\nNär conversations.im väljs som leverantör kommer du kunna kommunicera med användare hos andra leverantörer genom att ge dem ditt Jabber ID.</string> <string name="your_full_jid_will_be">Ditt jabber ID blir: %s</string> <string name="create_account">Skapa konto</string> <string name="use_own_provider">Använd min egen leverantör</string> @@ -658,4 +661,25 @@ <string name="pref_delete_omemo_identities_summary">Regenerera din OMEMO-nyckel. Alla dina kontakter kommer att behöva verifiera dig igen. Använd detta endast som en sista utväg.</string> <string name="delete_selected_keys">Ta bort valda nycklar</string> <string name="error_publish_avatar_offline">Du måste vara ansluten för att publicera din avatarbild</string> + <string name="show_error_message">Visa felmeddelande</string> + <string name="error_message">Felmeddelande</string> + <string name="data_saver_enabled">Databesparing</string> + <string name="data_saver_enabled_explained">Ditt operativsystem begränsar Internet-access för Conversations i när den är i bakgrunden. För att få notifieringar vid nya meddelanden behöver du ge Conversations obegränsad access när databesparing är påslaget.\nConversations kommer ändå försöka minska dataanvändningen när det är möjligt.</string> + <string name="device_does_not_support_data_saver">Din enhet stödjer inte att deaktivera databesparing för Conversations.</string> + <string name="error_unable_to_create_temporary_file">Kunde inte skapa tillfällig fil</string> + <string name="this_device_has_been_verified">Denna enhet har verifierats</string> + <string name="copy_fingerprint">Kopiera fingeravtryck</string> + <string name="all_omemo_keys_have_been_verified">Alla OMEMO nycklar har verifierats</string> + <string name="barcode_does_not_contain_fingerprints_for_this_conversation">Streckkoden innehåller inte fingeravtryck för den här konversationen.</string> + <string name="verified_fingerprints">Verifierade fingeravtryck</string> + <string name="use_camera_icon_to_scan_barcode">Använd kameran för att scanna en kontakts streckkod</string> + <string name="please_wait_for_keys_to_be_fetched">Vänta medans nycklar hämtas</string> + <string name="share_as_barcode">Dela som streckkod</string> + <string name="share_as_uri">Dela som XMPP URI</string> + <string name="share_as_http">Dela som HTTP länk</string> + <string name="pref_blind_trust_before_verification">Blint förtroende före verifiering</string> + <string name="pref_blind_trust_before_verification_summary">Lita automatiskt på alla nya enheter för kontakter som inte har blivit verifierade tidigare, och be om manuell bekräftelse varje gång en verifierad kontakt lägger till en ny enhet.</string> + <string name="blindly_trusted_omemo_keys">Lita blint på OMEMO-nycklar</string> + <string name="not_trusted">Ej betrodd</string> + <string name="invalid_barcode">Ogiltig 2D-streckkod</string> </resources> diff --git a/src/main/res/values-tr-rTR/strings.xml b/src/main/res/values-tr-rTR/strings.xml index f0c07049..364b2c0e 100644 --- a/src/main/res/values-tr-rTR/strings.xml +++ b/src/main/res/values-tr-rTR/strings.xml @@ -1,12 +1,12 @@ <?xml version='1.0' encoding='UTF-8'?> <resources> <string name="action_settings">Ayarlar</string> - <string name="action_add">Yeni sohbet</string> + <string name="action_add">Yeni konuşma</string> <string name="action_accounts">Hesapları yönet</string> - <string name="action_end_conversation">Sohbeti sonlandır</string> + <string name="action_end_conversation">Konuşmayı 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_muc_details">Toplantı bilgileri</string> + <string name="action_secure">Güvenli konuşma</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> @@ -17,36 +17,36 @@ <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_conference_details">Toplantı 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_sharewith">Konuşmayla paylaş</string> + <string name="title_activity_start_conversation">Konuşmayı 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="unread_conversations">okunmamış konuşmalar</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="pgp_message">OpenPGP şifreli ileti</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="remove_contact_text">%s kişisini listenizden silmek istiyor musunuz? Bu kişiyle yaptığınız konuşmalar 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="block_domain_text">%s üzerinden gelen tüm 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="remove_bookmark_text">%s yer imini silmek istiyor musunuz? Bu yer imiyle ilintili konuşma 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="start_conversation">Konuşma başlat</string> <string name="invite_contact">Kişi davet et</string> <string name="contacts">Kişiler</string> <string name="cancel">İptal et</string> @@ -74,13 +74,13 @@ <string name="preparing_images">Resimler iletilmek üzere hazırlanıyor</string> <string name="sharing_files_please_wait">Dosyalar Paylaşılıyor. Lütfen bekleyin...</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="clear_conversation_history">Konuşma geçmişini sil</string> + <string name="clear_histor_msg">Bu konuşmadaki tüm iletileri silmek istiyor musunuz?\n\n<b>Uyarı:</b>Başka aygıtlardaki 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">Cihaz seç</string> + <string name="also_end_conversation">Sonrasında bu konuşmayı sonlandır</string> + <string name="choose_presence">Aygıt seç</string> <string name="send_unencrypted_message">Şifrelenmemiş ileti gönder</string> - <string name="send_message_to_x">%s kişisine mesaj gönder</string> + <string name="send_message_to_x">%s kişisine 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> @@ -96,17 +96,17 @@ <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="contact_has_no_pgp_key">Kişi ortak anahtarını yayımlamadığı 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="contacts_have_no_pgp_keys">Kişiler ortak anahtarlarını yayımlamadığı için Conversations iletilerinizi şifreleyemiyor.\n\n<small>Lütfen kişilerden OpenPGP’yi ayarlamalarını isteyin.</small></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_accept_files_summary">…‘den küçük olan dosyaları kendiliğinden kabul et</string> <string name="pref_attachments">Ekler</string> <string name="pref_return_to_previous">Hızlı Paylaşım</string> - <string name="pref_return_to_previous_summary">Bir şey paylaştıktan sonra bir grup sohbetine dönmek yerine önceki etkinliğe dön</string> + <string name="pref_return_to_previous_summary">Bir şey paylaştıktan sonra bir grup konuşmasına dönmek yerine önceki etkinliğe dön</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> @@ -117,12 +117,12 @@ <string name="pref_sound">Zil sesi</string> <string name="pref_sound_summary">Yeni bir ileti geldiğinde sesli bildir</string> <string name="pref_notification_grace_period">Mühlet</string> - <string name="pref_notification_grace_period_summary">Başka bir cihaz üstünde etkinlik algılandığında Conversations\'ın sessiz kalma süresi</string> + <string name="pref_notification_grace_period_summary">Başka bir aygıt üstünde etkinlik algılandığında Conversations\'ın sessiz kalma süresi</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_confirm_messages_summary">Onların iletilerini aldığınızda ve okuduğunuzda, kişilerinizin bunu bilmesini sağlayın</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> @@ -133,9 +133,9 @@ <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="send_presence_updates">Çevrim içi durum bildirimi gönder</string> + <string name="receive_presence_updates">Çevrim içi durum bildirimi al</string> + <string name="ask_for_presence_updates">Çevrim iç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> @@ -146,9 +146,9 @@ <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_online">Çevrim içi</string> <string name="account_status_connecting">Bağlanıyor\u2026</string> - <string name="account_status_offline">Çevrimdışı</string> + <string name="account_status_offline">Çevrim dışı</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> @@ -167,13 +167,13 @@ <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="openpgp_has_been_published">OpenPGP genel anahtar yayınlandı.</string> - <string name="republish_pgp_keys">OpenPGP genel anahtarınızı yeniden yayınlamayı unutmayın!</string> + <string name="mgmt_account_publish_avatar">Avatar yayımla</string> + <string name="mgmt_account_publish_pgp">OpenPGP genel anahtarını yayımla</string> + <string name="openpgp_has_been_published">OpenPGP genel anahtar yayımlandı.</string> + <string name="republish_pgp_keys">OpenPGP genel anahtarınızı yeniden yayımlamayı unutmayın!</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="mgmt_account_delete_confirm_text">Hesabınızı silerseniz tüm konuşma 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> @@ -185,13 +185,13 @@ <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_online">çevrim içi</string> + <string name="contact_status_free_to_chat">konuşma 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="contact_status_offline">çevrim dışı</string> + <string name="muc_details_conference">Toplantı</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> @@ -214,7 +214,7 @@ <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="install_openkeychain">Şifreli ileti. 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> @@ -227,17 +227,17 @@ <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="other_devices">Diğer aygıtlar</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="conferences">Toplantılar</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="join_conference">Toplantıya 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> @@ -246,31 +246,31 @@ <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/nick</string> + <string name="conference_address">Toplantı adresi</string> + <string name="conference_address_example">oda@toplanti.ornek.com/nick</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="edit_subject_hint">Bu grup sohbetin konusu</string> - <string name="joining_conference">Grup sohbete katiliyor</string> + <string name="action_edit_subject">Toplantı konusunu düzenle</string> + <string name="edit_subject_hint">Bu toplantının konusu</string> + <string name="joining_conference">Toplantıya katılıyor...</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="publish">Yayımla</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="publish_avatar_explanation">Lütfen dikkat: Çevrim içi durum bildirimi güncellemelerinize abone olan herkes bu resmi görebilir.</string> + <string name="publishing">Yayımlanıyor…</string> + <string name="error_publish_avatar_server_reject">Sunucu yayımladığınızı 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="error_publish_avatar_no_server_support">Sunucunuz avatar yayımlanması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="send_private_message_to">%s kişisine özel ileti gönder</string> <string name="connect">Bağlan</string> <string name="account_already_exists">Bu hesap zaten mevcut</string> <string name="next">Sonraki</string> @@ -280,19 +280,19 @@ <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="conference_requires_password">Toplantı 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="missing_presence_updates">Kişinin çevrim içi durum bildirimi güncellemesi kayıp</string> <string name="missing_presence_subscription">Durum bildirimi aboneliği eksik.</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_presence_updates">Lütfen öncelikle kişiden çevrim iç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="without_mutual_presence_updates"><b>Uyarı:</b> Karşılıklı çevrim içi durum bildirimi güncellemeleri olmaksızın bunu göndermeniz beklenmedik sorunlara neden olabilir.\n\n\n\n<small>Çevrim içi durum bildirimi aboneliklerinizi denetlemek 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_force_encryption_summary">Her zaman şifrelenmiş ileti gönder (toplantılar dışında)</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> @@ -306,31 +306,32 @@ <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_larger_font">Yazı tipini büyüt</string> + <string name="pref_use_larger_font_summary">Uygulamanın tümünde büyük yazı tipi 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_indicate_received_summary">Alınan iletiler, 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="pref_conference_name">Toplantı adı</string> + <string name="pref_conference_name_summary">Toplantıları tanımlamak için JID yerine odanın konusunu kullan</string> + <string name="pref_autojoin">Toplantılara kendiliğinden katıl</string> + <string name="pref_autojoin_summary">Toplantı yer imlerinde kendiliğinden katıl seçeneğine uy</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="conference_shutdown">Grup sohbet sona erdi</string> - <string name="conference_unknown_error">Artık bu grup sohbet içerisinde değilsiniz.</string> + <string name="conference_banned">Bu toplantıda engellendiniz</string> + <string name="conference_members_only">Bu toplantı yalnızca üyelere açıktır</string> + <string name="conference_kicked">Bu toplantıdan atıldınız</string> + <string name="conference_shutdown">Toplantı sona erdi</string> + <string name="conference_unknown_error">Artık bu toplantı içerisinde değilsiniz.</string> <string name="using_account">%s hesabını kullanarak</string> - <string name="checking_x">HTTP sunucusundaki %s \'leri kontrol ediyor</string> + <string name="checking_x">HTTP sunucusundaki %s denetleniyor</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="check_x_filesize_on_host">%2$s üzerindeki %1$s boyutunu kontrol edin</string> + <string name="check_x_filesize">%s boyutunu denetle</string> + <string name="check_x_filesize_on_host">%2$s üzerindeki %1$s boyutunu denetle</string> <string name="message_options">İleti seçenekleri</string> <string name="copy_text">Metni kopyala</string> + <string name="select_text">Metni seç</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> @@ -338,8 +339,8 @@ <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="scan_qr_code">2B Barkod Tara</string> + <string name="show_qr_code">2B Barkod 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> @@ -379,22 +380,22 @@ <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="are_you_sure_verify_fingerprint">Kişinizin OTR parmak izini doğrulamak istediğinize emin misiniz?</string> + <string name="pref_show_dynamic_tags">Canlı 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_with">… ile toplantı başlat</string> + <string name="no_conference_server_found">Bir toplantı sunucusu bulunamadı</string> + <string name="conference_creation_failed">Toplantı başlatılamadı!</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="wipe_omemo_pep">PEP’teki diğer aygıtları sil</string> + <string name="clear_other_devices">Aygıtları sil</string> + <string name="clear_other_devices_desc">OMEMO bildirimindeki diğer aygıtların hepsini silmek istediğinizden emin misiniz? Aygıtları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> @@ -408,8 +409,8 @@ <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="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 yalnızca 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. Yanıtın harf duyarlı olması gerekmektedir.</string> + <string name="smp_explain_answer">Kişi parmak izinizi onaylamak için yalnızca 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> @@ -417,11 +418,11 @@ <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="enable_all_accounts">Tüm hesapları etkinleştir</string> + <string name="disable_all_accounts">Tüm 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">Çevrimdışı</string> + <string name="no_role">Çevrim dışı</string> <string name="outcast">Bağlantısız</string> <string name="member">Üye</string> <string name="advanced_mode">Gelişmiş kip</string> @@ -429,21 +430,21 @@ <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="remove_from_room">Toplantıdan 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_from_conference">Toplantıdan engelle</string> + <string name="removing_from_public_conference">%s kişisini herkese açık toplantıdan atmaya çalışıyorsunuz. Bunu yalnızca bu kullanıcıyı süresiz engelleyerek 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="public_conference">Herkese açık toplantı</string> + <string name="private_conference">Özel, yalnızca üyelere açık toplantı</string> + <string name="conference_options">Toplantı seçenekleri</string> + <string name="members_only">Özel, yalnızca ü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="modified_conference_options">Değiştirilmiş toplantı seçenekleri!</string> + <string name="could_not_modify_conference_options">Toplantı 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> @@ -464,24 +465,24 @@ <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="avatar_has_been_published">Avatar yayımlandı!</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="hide_offline">Çevrim dışıları 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="pref_chat_states_summary">Siz onlara ileti yazarken kişilerinizin bunu bilmesini sağlayı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="title_undo_swipe_out_muc">Toplantıdan 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_dont_trust_system_cas_summary">Tüm 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> @@ -501,12 +502,12 @@ <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="user_has_left_conference">%s toplantıdan 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="conference_name">Grup sohbet ismi</string> - <string name="invalid_conference_name">Bu grup sohbet adı geçerli değil</string> + <string name="conference_name">Toplantı adı</string> + <string name="invalid_conference_name">Bu toplantı 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> @@ -519,11 +520,11 @@ <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_away_when_screen_off_summary">Ekran kapandığında çevrim iç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_treat_vibrate_as_silent">titreş modunu sessiz mod olarak değerlendir</string> - <string name="pref_treat_vibrate_as_silent_summary">cihazınız titreşim modundaysa durum bildiriminizi müsahit değil olarak değiştirir</string> + <string name="pref_treat_vibrate_as_silent_summary">Aygıtınız titreşim kipindeyken durum bildiriminizi uygun 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> @@ -541,10 +542,10 @@ <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="device_does_not_support_certificates">Aygıtınız seçilen istemci sertifikalarını desteklemiyor!</string> <string name="pref_connection_options">Bağlantı</string> <string name="pref_use_tor">Tor üzerinden bağlan</string> - <string name="pref_use_tor_summary">Bütün bağlantıları Tor ağı üzerinden aktar. Orbot gerekir.</string> + <string name="pref_use_tor_summary">Tüm bağlantıları Tor ağı üzerinden aktar. Orbot gerekir.</string> <string name="account_settings_hostname">Sunucu adı</string> <string name="account_settings_port">Port</string> <string name="hostname_or_onion">Sunucu- veya .onion-Address</string> @@ -561,7 +562,7 @@ <string name="shared_text_with_x">%s ile paylaşılan metin</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="sync_with_contacts_long">Conversations XMPP listenizi telefon rehberinizle eşleştirerek kişilerin tam isimlerini ve avatarlarını göstermek istiyor.\n\nConversations telefon rehberinizi yalnızca okuyacak ve onları sunucunuza yüklemeden eşleştirecek.\n\nŞimdi telefon rehberinize erişilmesine izin vermeniz istenecek.</string> <string name="certificate_information">Sertifika Bilgisi</string> <string name="certificate_subject">Konu</string> <string name="certificate_issuer">Veren</string> @@ -571,23 +572,23 @@ <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_only_when_highlighted">Yalnızca vurguluysa uyar</string> <string name="notify_never">Uyarılar devre dışı</string> <string name="notify_paused">Uyarılar geçici olarak durduruldu</string> <string name="pref_picture_compression">Resimleri sıkıştır</string> <string name="pref_picture_compression_summary">Resimleri yeniden boyutlandır ve sıkıştır</string> <string name="always">Her zaman</string> - <string name="automatically">Otomatik olarak</string> + <string name="automatically">Kendiliğinden</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="battery_optimizations_enabled_explained">Aygıtınız Conversations üzerinde yoğun pil iyileştirmesi yaptığı için bildirimlerde gecikmeler olabilir üstelik 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">Aygıtınız Conversations üzerinde yoğun pil iyileştirmesi yaptığı için bildirimlerde gecikmeler olabilir üstelik 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="no_accounts">(Etkin hesap bulunmuyor)</string> <string name="this_field_is_required">Bu alan zorunludur</string> <string name="correct_message">ileti düzelt</string> <string name="send_corrected_message">Düzeltilmiş iletiyi gönder</string> - <string name="no_keys_just_confirm">Bu kişiye zaten güveniyor durumdasınız. \"tamam\" seçeneğini işaretleyerek, sadece %s \'in bu grup sohbete katılabileceğini teyid ediyorsunuz.</string> + <string name="no_keys_just_confirm">Bu kişiye zaten güveniyor durumdasınız. \"Tamam\"ı seçerek, yalnızca %s\'in bu toplantıya katılabileceğini onaylıyorsunuz.</string> <string name="select_image_and_crop">Resmi seçip kırpın</string> <string name="this_account_is_disabled">Bu hesabı devre dışı bıraktınız</string> <string name="security_error_invalid_file_access">Güvenlik hatası: Geçersiz dosya erişimi</string> @@ -603,37 +604,37 @@ <string name="pref_manually_change_presence_summary">Durumunuzu değiştirmek için avatarınıza dokunun</string> <string name="change_presence">Durum Değiştir</string> <string name="status_message">Durum iletisi</string> - <string name="all_accounts_on_this_device">Bu cihazdaki tüm hesaplara uygula</string> + <string name="all_accounts_on_this_device">Bu aygıttaki tüm hesaplara uygula</string> <string name="presence_chat">Sohbet için uygun</string> - <string name="presence_online">Çevrimiçi</string> + <string name="presence_online">Çevrim içi</string> <string name="presence_away">Uzakta</string> <string name="presence_xa">Mevcut Değil</string> <string name="presence_dnd">Meşgul</string> <string name="secure_password_generated">Güvenli bir parola oluşturuldu</string> - <string name="device_does_not_support_battery_op">Cihazınız pil optimizasyonunu devre dışı bırakmayı desteklemiyor</string> + <string name="device_does_not_support_battery_op">Aygıtınız pil iyileştirmesini devre dışı bırakmayı desteklemiyor</string> <string name="show_password">Parola göster</string> - <string name="registration_please_wait">Hesap oluşturulamadı: Sonra tekrar deneyin</string> + <string name="registration_please_wait">Hesap oluşturulamadı: Sonra yeniden deneyin</string> <string name="registration_password_too_weak">Kayıt Başarısız: Parola çok zayıf</string> - <string name="create_conference">Grup Sohbet başlat</string> - <string name="join_or_create_conference">Grup Sohbete katıl veya başlat</string> + <string name="create_conference">Toplantı başlat</string> + <string name="join_or_create_conference">Toplantıya katıl veya başlat</string> <string name="conference_subject">Konu</string> <string name="choose_participants">Katılımcıları seç</string> - <string name="creating_conference">Grup Sohbet başlatılıyor...</string> + <string name="creating_conference">Toplantı başlatılıyor...</string> <string name="invite_again">Yeniden davet et</string> <string name="gp_short">Kısa</string> <string name="gp_medium">Orta</string> <string name="gp_long">Uzun</string> - <string name="pref_broadcast_last_activity">Son kullanıcı etkileşimini yayınla</string> + <string name="pref_broadcast_last_activity">Son kullanıcı etkileşimini yayımla</string> <string name="pref_broadcast_last_activity_summary">Tüm kişileriniz ne zaman Conversations kullandığınızı görsün</string> <string name="pref_privacy">Mahremiyet</string> - <string name="pref_theme_options">Tema</string> + <string name="pref_theme_options">Gövde</string> <string name="pref_theme_options_summary">Renk paletini seçin</string> - <string name="pref_theme_light">Açık tema</string> - <string name="pref_theme_dark">Koyu tema</string> + <string name="pref_theme_light">Açık gövde</string> + <string name="pref_theme_dark">Koyu gövde</string> <string name="pref_use_green_background">Yeşil arka plan</string> <string name="pref_use_green_background_summary">Gelen iletiler için yeşil arka plan kullan</string> <string name="unable_to_connect_to_keychain">OpenKeychain\'e bağlanılamıyor</string> - <string name="this_device_is_no_longer_in_use">Bu cihaz artık kullanılmıyor</string> + <string name="this_device_is_no_longer_in_use">Bu aygıt artık kullanılmıyor</string> <string name="type_pc">Bilgisayar</string> <string name="type_phone">Mobil telefon</string> <string name="type_tablet">Tablet</string> @@ -650,11 +651,32 @@ <string name="missing_presence_subscription_with_x">%s ile durum bildirimi aboneliği eksik.</string> <string name="missing_keys_from_x">%s den eksik OMEMO anahtarları var.</string> <string name="missing_omemo_keys">Eksik OMEMO anahtarları var.</string> - <string name="wrong_conference_configuration">Bu özel ve anonim olmayan bir sohbet.</string> - <string name="this_conference_has_no_members">Bu konferansta herhangi bir üye yok. </string> + <string name="wrong_conference_configuration">Bu özel ve adsız olmayan bir toplantı.</string> + <string name="this_conference_has_no_members">Bu toplantıda herhangi bir üye yok.</string> <string name="report_jid_as_spammer">Bu kişiyi istenmeyen ileti gönderen olarak rapor et.</string> <string name="pref_delete_omemo_identities">OMEMO kimiliğini sil</string> - <string name="pref_delete_omemo_identities_summary">OMEMO anahtarını tekrar oluştur. Tüm kişilerinizin sizi tekrar doğrulaması gerekecek. Bunu sadece son çare olarak kullanın.</string> + <string name="pref_delete_omemo_identities_summary">OMEMO anahtarını yeniden oluştur. Tüm kişilerinizin sizi yeniden doğrulaması gerekecek. Bunu yalnızca son çare olarak kullanın.</string> <string name="delete_selected_keys">Seçilen anahtarları sil</string> - <string name="error_publish_avatar_offline">Avatarınızı yayınlamak için bağlı olmalısınız.</string> + <string name="error_publish_avatar_offline">Avatarınızı yayımlamak için bağlı olmalısınız.</string> + <string name="show_error_message">Hata iletisini göster</string> + <string name="error_message">Hata İletisi</string> + <string name="data_saver_enabled">Veri tasarrufu etkin</string> + <string name="data_saver_enabled_explained">İşletim sisteminiz Conversations arka planda çalışırken İnternet erişimini sınırlıyor. Yeni ileti alındığında uyarı gelmesi için Conversations\'a veri tasarrufu etkin durumdayken sınırsız erişim vermeniz gerekiyor.\nConversations mümkün olan durumlarda veri tasarrufu için çaba harcar.</string> + <string name="device_does_not_support_data_saver">Aygıtınız Conversations için Veri tasarrufunu devre dışı bırakmayı desteklemiyor</string> + <string name="error_unable_to_create_temporary_file">Geçici dosya oluşturulamıyor</string> + <string name="this_device_has_been_verified">Bu aygıt doğrulandı</string> + <string name="copy_fingerprint">Parmak izini kopyala</string> + <string name="all_omemo_keys_have_been_verified">Tüm OMEMO anahtarları doğrulandı</string> + <string name="barcode_does_not_contain_fingerprints_for_this_conversation">Barkod, bu konuşma için parmak izlerini içermiyor.</string> + <string name="verified_fingerprints">Doğrulanmış parmak izleri</string> + <string name="use_camera_icon_to_scan_barcode">Bir kişinin barkodunu taramak için kamerayı kullan</string> + <string name="please_wait_for_keys_to_be_fetched">Anahtarların alınması için lütfen bekle</string> + <string name="share_as_barcode">Barkod olarak paylaş</string> + <string name="share_as_uri">XMPP URI\'si olarak paylaş</string> + <string name="share_as_http">HTTP bağlantısı olarak paylaş</string> + <string name="pref_blind_trust_before_verification">Doğrulamadan Önce Kör Güven</string> + <string name="pref_blind_trust_before_verification_summary">Kişilerin daha önce doğrulanmamış tüm yeni aygıtlarına kendiliğinden güven ve doğrulanmış kişi her yeni aygıt eklediğinde elle doğrulama iste.</string> + <string name="blindly_trusted_omemo_keys">Körü körüne güvenilen OMEMO anahtarları</string> + <string name="not_trusted">Güvenilmeyen</string> + <string name="invalid_barcode">Geçersiz 2D barkod</string> </resources> diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 576c7310..421bf980 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -121,14 +121,12 @@ <string name="pref_never_send_crash">Ніколи не надсилати звіти про збої</string> <string name="pref_never_send_crash_summary">Надсилаючи траси стеку викликів Ви допомагаєте розробці Розмов, яка продовжується</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> <string name="error">Сталася помилка</string> <string name="pref_grant_presence_updates">Давати оновлення про присутність</string> - <string name="pref_grant_presence_updates_summary">Попередньо давати та запитувати підписку на присутність для контактів, які Ви створюєте</string> <string name="subscriptions">Підписки</string> <string name="your_account">Ваш обліковий запис</string> <string name="keys">Ключі</string> @@ -335,8 +333,6 @@ <string name="url_copied_to_clipboard">URL скопійовано до комірки обміну</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> @@ -374,7 +370,6 @@ <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> @@ -467,7 +462,6 @@ <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> diff --git a/src/main/res/values-v21/themes.xml b/src/main/res/values-v21/themes.xml index d6612552..aa5e825b 100644 --- a/src/main/res/values-v21/themes.xml +++ b/src/main/res/values-v21/themes.xml @@ -60,6 +60,7 @@ <item name="attr/icon_settings">@drawable/ic_settings_black_24dp</item> <item name="attr/icon_import_export">@drawable/ic_import_export_white_24dp</item> <item name="attr/icon_share">@drawable/ic_share_white_24dp</item> + <item name="attr/icon_scan_qr_code">@drawable/ic_camera_alt_white_24dp</item> <item name="attr/icon_notifications">@drawable/ic_notifications_black54_24dp</item> <item name="attr/icon_notifications_off">@drawable/ic_notifications_off_black54_24dp</item> @@ -126,6 +127,7 @@ <item name="attr/icon_settings">@drawable/ic_settings_white_24dp</item> <item name="attr/icon_import_export">@drawable/ic_import_export_white_24dp</item> <item name="attr/icon_share">@drawable/ic_share_white_24dp</item> + <item name="attr/icon_scan_qr_code">@drawable/ic_camera_alt_white_24dp</item> <item name="attr/icon_notifications">@drawable/ic_notifications_white_24dp</item> <item name="attr/icon_notifications_off">@drawable/ic_notifications_off_white_24dp</item> diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml index 0a1f24b6..dd2a1d02 100644 --- a/src/main/res/values-vi/strings.xml +++ b/src/main/res/values-vi/strings.xml @@ -106,13 +106,11 @@ <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> @@ -301,8 +299,6 @@ <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> @@ -339,7 +335,6 @@ <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> @@ -430,7 +425,6 @@ <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> diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index a2585623..e0705cb0 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -122,14 +122,12 @@ <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="openpgp_error">OpenKeychain 报告了一个错误</string> <string name="error_decrypting_file">解密文件时出现 I/O 错误</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="subscriptions">关注</string> <string name="your_account">你的账号</string> <string name="keys">密钥</string> @@ -157,7 +155,9 @@ <string name="account_status_regis_success">注册完成</string> <string name="account_status_regis_not_sup">服务器不支持注册</string> <string name="account_status_security_error">安全错误</string> + <string name="account_status_policy_violation">违反政策</string> <string name="account_status_incompatible_server">服务器不兼容</string> + <string name="account_status_stream_error">流错误</string> <string name="encryption_choice_unencrypted">未加密</string> <string name="encryption_choice_otr">OTR</string> <string name="encryption_choice_pgp">OpenPGP</string> @@ -281,6 +281,7 @@ <string name="conference_requires_password">讨论组设有密码</string> <string name="enter_password">输入密码</string> <string name="missing_presence_updates">缺少在线联系人更新</string> + <string name="missing_presence_subscription">缺少在线订阅</string> <string name="request_presence_updates">请先发送更新在线联系人的请求。\n\n<small>以判断您的联系人所用的客户端类型。</small></string> <string name="request_now">现在发送请求</string> <string name="delete_fingerprint">删除指纹</string> @@ -328,6 +329,7 @@ <string name="check_x_filesize_on_host">在 %2$s 上检查 %1$s 的大小</string> <string name="message_options">消息选项</string> <string name="copy_text">拷贝文本</string> + <string name="select_text">选择文本</string> <string name="copy_original_url">拷贝原始URL</string> <string name="send_again">再次发送</string> <string name="file_url">文件 </string> @@ -335,8 +337,6 @@ <string name="url_copied_to_clipboard">已经拷贝 URL 到剪贴板</string> <string name="message_copied_to_clipboard">消息已经拷贝到剪贴板</string> <string name="image_transmission_failed">图片传送失败</string> - <string name="scan_qr_code">扫描二维码</string> - <string name="show_qr_code">显示二维码</string> <string name="show_block_list">显示屏蔽列表</string> <string name="account_details">账户详情</string> <string name="verify_otr">验证 OTR</string> @@ -376,7 +376,6 @@ <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> @@ -469,7 +468,6 @@ <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> @@ -512,6 +510,7 @@ <string name="pref_use_white_background_summary">收到的消息将显示为白底黑字</string> <string name="account_status_tor_unavailable">Tor network 不可用</string> <string name="account_status_bind_failure">绑定失败</string> + <string name="account_status_host_unknown">服务器不能为域名做出响应</string> <string name="server_info_broken">损坏</string> <string name="pref_presence_settings">存在</string> <string name="pref_away_when_screen_off">关闭屏幕时离开</string> @@ -591,6 +590,8 @@ <string name="security_error_invalid_file_access">安全错误:文件访问权限无效</string> <string name="no_application_to_share_uri">没有可以分享此链接的应用</string> <string name="share_uri_with">分享链接...</string> + <string name="welcome_text">XMPP 是一个独立协议的提供者。你可以用这个客户端连接任何你选择的 XMPP 服务器。\n为了让你更便捷,我们让你可以在 conversations.im¹ 上方便的注册账号;特意为使用 Conversations 的人准备。</string> + <string name="magic_create_text">我们将会引导你完成在 conversations.im¹ 上注册账号的过程。\n当获得了 conversations.im 提供的账号以后,你可以通过添加使用其他提供商的好友的完整 Jabber ID 来与他们联络。</string> <string name="your_full_jid_will_be">您完整的 Jabble ID 是:%s </string> <string name="create_account">创建账户</string> <string name="use_own_provider">使用我自己的服务端</string> @@ -636,15 +637,26 @@ <string name="type_web">浏览器</string> <string name="type_console">控制台</string> <string name="payment_required">需要付款</string> + <string name="missing_internet_permission">缺少互联网权限</string> + <string name="me">我</string> + <string name="contact_asks_for_presence_subscription">联系人请求在线订阅</string> <string name="allow">允许</string> <string name="no_permission_to_access_x">没有访问 %s 的许可</string> <string name="remote_server_not_found">找不到远程服务器</string> <string name="unable_to_update_account">无法更新账户</string> + <string name="missing_presence_subscription_with_x">缺少与 %s 的在线订阅。</string> <string name="missing_keys_from_x">缺少来自 %s 的 OMEMO 密钥。</string> + <string name="missing_omemo_keys">缺少 OMEMO 密钥</string> <string name="wrong_conference_configuration">这不是一个私密,非匿名的会议室。</string> <string name="this_conference_has_no_members">这个会议室中没有成员。</string> <string name="report_jid_as_spammer">报告这个 JID 发送不想要的消息。</string> <string name="pref_delete_omemo_identities">删除 OMEMO 身份</string> <string name="pref_delete_omemo_identities_summary">重新生成你的 OMEMO 密钥。你的所有联系人不得不对你进行再认证。请将此作为最后的办法。</string> <string name="delete_selected_keys">删除选择的密钥</string> + <string name="error_publish_avatar_offline">你需要连接才能发布头像</string> + <string name="show_error_message">显示出错消息</string> + <string name="error_message">出错消息</string> + <string name="data_saver_enabled">省流量模式已激活</string> + <string name="data_saver_enabled_explained">您的操作系统禁止本程序在后台运行时访问互联网。为了收到新消息提示,您需要在省流量模式下允许本程序不受限制地访问互联网。\n本程序仍会尽可能节省流量。</string> + <string name="device_does_not_support_data_saver">该设备不支持禁用省流量模式</string> </resources> diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml index 00557f09..e8df9d29 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -5,126 +5,173 @@ <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_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_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="minutes_ago">%d分鐘前</string> + <string name="unread_conversations">未讀會話</string> <string name="sending">正在發送…</string> - <string name="nick_in_use">該用戶名稱已被使用</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="owner">所有者</string> <string name="moderator">版主</string> - <string name="participant">成員</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="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="start_conversation">開始會話</string> + <string name="invite_contact">邀請連絡人</string> + <string name="contacts">連絡人</string> <string name="cancel">取消</string> - <string name="add">新增</string> + <string name="set">設置</string> + <string name="add">添加</string> <string name="edit">編輯</string> <string name="delete">刪除</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="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="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="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="preparing_image">準備傳輸圖像</string> + <string name="preparing_images">正在傳輸圖像</string> + <string name="sharing_files_please_wait">正在分享文件,請稍候…</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="send_otr_message">發送 OTR 加密訊息</string> - <string name="send_pgp_message">發送 OpenPGP 加密訊息</string> - <string name="your_nick_has_been_changed">用戶名稱修改成功</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_unencrypted_message">發送未加密的資訊</string> + <string name="send_message_to_x">發信息給 %s</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="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="restart">重新啟動</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="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="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="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_summary">自動接收小於 … 的文件</string> + <string name="pref_attachments">附件</string> + <string name="pref_return_to_previous">快速分享</string> + <string name="pref_return_to_previous_summary">分享後立即回到之前活動的應用程式,而非打開會話</string> + <string name="pref_notification_settings">通知</string> <string name="pref_notifications">通知</string> - <string name="pref_notifications_summary">收到新訊息時通知</string> + <string name="pref_notifications_summary">收到新消息時通知</string> <string name="pref_vibrate">震動</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="openpgp_error">OpenKeychain 回報了一個錯誤</string> - <string name="error_decrypting_file">解密文件時出現 I/O 錯誤</string> + <string name="pref_vibrate_summary">收到新消息時震動</string> + <string name="pref_led">LED 燈提示</string> + <string name="pref_led_summary">收到新消息時閃爍通知燈</string> + <string name="pref_sound">鈴聲</string> + <string name="pref_sound_summary">收到新消息時響鈴</string> + <string name="pref_notification_grace_period">靜默期限</string> + <string name="pref_notification_grace_period_summary">發現在其它設備上的活動後,Conversations保持安靜的時間</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_ui_options">UI</string> + <string name="openpgp_error">OpenKeychain 報告了一個錯誤</string> + <string name="error_decrypting_file">解密檔時出現 I/O 錯誤</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="error">產生了一個錯誤</string> + <string name="pref_grant_presence_updates">同意更新線上連絡人</string> <string name="subscriptions">關注</string> - <string name="your_account">你的帳戶</string> - <string name="keys">鑰匙</string> - <string name="send_presence_updates">發送狀態訊息</string> - <string name="receive_presence_updates">接收狀態訊息</string> - <string name="ask_for_presence_updates">關注狀態訊息</string> + <string name="your_account">你的帳號</string> + <string name="keys">金鑰</string> + <string name="send_presence_updates">發送線上連絡人列表更新</string> + <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="error_file_not_found">找不到文件</string> - <string name="error_io_exception">一般的 I/O 錯誤。是存儲空間不足嗎?</string> - <string name="error_security_exception_during_image_copy">你用來選擇圖片的 app 沒有給予足夠權限我們去讀取文件。\n\n<small>請使用另一文件管理器來選擇圖片</small></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">您用來選擇圖片的 app 沒有給予足夠許可權支持我們讀取檔。\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_disabled">暫時不可用</string> + <string name="account_status_online">線上</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_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="account_status_regis_not_sup">伺服器不支援註冊</string> + <string name="account_status_security_error">安全錯誤</string> + <string name="account_status_policy_violation">違反政策</string> + <string name="account_status_incompatible_server">伺服器不相容</string> + <string name="account_status_stream_error">流錯誤</string> + <string name="encryption_choice_unencrypted">未加密</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_disable">暫時停用</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_publish_pgp">發布 OpenPGP 公共鑰匙</string> + <string name="mgmt_account_publish_pgp">發佈 OpenPGP 公開金鑰</string> + <string name="openpgp_has_been_published">OpenPGP 公開金鑰已發佈</string> + <string name="republish_pgp_keys">記得重新發佈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="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">Jabber ID</string> <string name="account_settings_password">密碼</string> @@ -134,97 +181,482 @@ <string name="confirm_password">確認密碼</string> <string name="passwords_do_not_match">密碼不一致</string> <string name="invalid_jid">該 Jabber ID 無效</string> - <string name="error_out_of_memory">空間不足,圖片過大</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_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_carbon_messages">XEP-0280: Message Carbons</string> - <string name="server_info_stream_management">XEP-0198: Stream Management</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="never_seen">未曾上線</string> - <string name="install_openkeychain">加密的訊息。請安裝 OpenKeychain 以解密。</string> - <string name="unknown_otr_fingerprint">未知的 OTR 指紋</string> - <string name="openpgp_messages_found">發現以 OpenPGP 加密的訊息</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_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_stream_management">XEP-0198: 流管理</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">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> + <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_selected_message">消息的 OTR 指紋</string> + <string name="openpgp_key_id">OpenPGP 金鑰 ID</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="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="create">新增</string> - <string name="contact_already_exists">聯絡人已存在</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> - <string name="save_as_bookmark">儲存為書籤</string> - <string name="delete_bookmark">刪除書籤</string> - <string name="bookmark_already_exists">該書籤已存在</string> + <string name="conference_address">討論群組地址</string> + <string name="conference_address_example">room@conference.example.com/nick</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="action_edit_subject">編輯討論群組主題</string> + <string name="edit_subject_hint">討論群組主題</string> + <string name="joining_conference">加入討論群組…</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="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="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="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="account_already_exists">該帳號已存在</string> <string name="next">下一步</string> - <string name="server_info_session_established">已建立連接</string> - <string name="additional_information">其他訊息</string> - <string name="skip">略過</string> + <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="disable_notifications_for_this_conversation">關閉該會話消息</string> <string name="enable">打開通知</string> - <string name="conference_requires_password">群組設有密碼</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> + <string name="missing_presence_updates">缺少線上連絡人更新</string> + <string name="missing_presence_subscription">缺少線上訂閱</string> + <string name="request_presence_updates">請先發送更新線上連絡人的請求。\n\n<small>以判斷您的連絡人所用的用戶端類型。</small></string> <string name="request_now">現在發送請求</string> <string name="delete_fingerprint">刪除指紋</string> - <string name="sure_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">強制要求端到端加密</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="pref_use_larger_font">增加字體大小</string> - <string name="pref_use_larger_font_summary">讓整個 app 界面使用更大號的字體</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="without_mutual_presence_updates"><b>警告:</b>在沒有相互更新線上連絡人的情況下發送將會出現未知問題。\n\n<small>前往連絡人詳情以驗證您訂閱的線上連絡人。</small></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_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">整個 app 介面使用較大的字體</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="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="conference_shutdown">討論群組已被關閉</string> + <string name="conference_unknown_error">你已不屬於此討論群組</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="check_x_filesize_on_host">在 %2$s 上檢查 %1$s 的大小</string> + <string name="message_options">消息選項</string> + <string name="copy_text">拷貝文本</string> + <string name="select_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> + <string name="image_transmission_failed">圖片傳送失敗</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 會話</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="pref_export_logs">匯出歷史記錄</string> + <string name="pref_export_logs_summary">將 conversations 的歷史日誌寫到 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="delete_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="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="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_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="ask_question">提出問題</string> + <string name="smp_explain_question">如果你和你的連絡人有一個共知的秘密(比如一個內部笑話或者僅僅只是上次見面時吃的午餐) 你可以使用這個秘密來驗證彼此的指紋。\n\n你的連絡人將以大小寫敏感的方式給出答案,你可以給出提示或問題。</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="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> + <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> + <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">Android App</string> + <string name="vcard">連絡人</string> + <string name="received_x_file">已經收到 %s</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_has_stopped_typing">%s 已停止輸入</string> + <string name="pref_chat_states">鍵盤輸入通知</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">Conversation 已關閉</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> + <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="other">%d 個證書已被刪除</item> + </plurals> + <plurals name="select_contact"> + <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="conference_name">討論群組名稱</string> + <string name="invalid_conference_name">該討論群組名稱無效</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="download_failed_could_not_write_file">下載失敗:不能寫入檔</string> + <string name="pref_use_white_background">使用白色背景</string> + <string name="pref_use_white_background_summary">收到的消息將顯示為白底黑字</string> + <string name="account_status_tor_unavailable">Tor network 不可用</string> + <string name="account_status_bind_failure">綁定失敗</string> + <string name="account_status_host_unknown">伺服器不能為功能變數名稱做出回應</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_treat_vibrate_as_silent">靜音模式開啟振動</string> + <string name="pref_treat_vibrate_as_silent_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_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="pref_use_tor">通過 Tor 連接</string> + <string name="pref_use_tor_summary">所有連接使用 Tor 網路傳輸,需要 Orbot</string> + <string name="account_settings_hostname">主機名稱</string> + <string name="account_settings_port">埠</string> + <string name="hostname_or_onion">伺服器 - 或者 .orion 地址</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="load_more_messages">載入更多消息</string> + <string name="shared_file_with_x">用 %s 分享文件</string> + <string name="shared_image_with_x">用 %s 分享圖片</string> + <string name="shared_images_with_x">圖片分享自 %s</string> + <string name="shared_text_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="pref_picture_compression">壓縮圖片</string> + <string name="pref_picture_compression_summary">裁剪並壓縮圖片</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> + <string name="correct_message">更正消息</string> + <string name="send_corrected_message">發送更正後的消息</string> + <string name="no_keys_just_confirm">你已信任此連絡人。選擇“完成”表示 %s 將成為此討論群組的一部分。</string> + <string name="select_image_and_crop">選擇照片並裁剪</string> + <string name="this_account_is_disabled">你已經禁用了此帳戶</string> + <string name="security_error_invalid_file_access">安全錯誤:檔存取權限無效</string> + <string name="no_application_to_share_uri">沒有可以分享此連結的應用</string> + <string name="share_uri_with">分享連結...</string> + <string name="welcome_text">XMPP 是一個獨立協議的提供者。你可以用這個用戶端連接任何你選擇的 XMPP 伺服器。\n為了讓你更便捷,我們讓你可以在 conversations.im¹ 上方便的註冊帳號;特意為使用 Conversations 的人準備。</string> + <string name="magic_create_text">我們將會引導你完成在 conversations.im¹ 上註冊帳號的過程。\n當獲得了 conversations.im 提供的帳號以後,你可以通過添加使用其他提供商的好友的完整 Jabber ID 來與他們聯絡。</string> + <string name="your_full_jid_will_be">您完整的 Jabble ID 是:%s </string> + <string name="create_account">創建帳戶</string> + <string name="use_own_provider">使用我自己的服務端</string> + <string name="pick_your_username">輸入您的用戶名</string> + <string name="pref_manually_change_presence">手動更改狀態</string> + <string name="pref_manually_change_presence_summary">點擊你的頭像來變更你的狀態</string> + <string name="change_presence">變更狀態</string> + <string name="status_message">狀態資訊</string> + <string name="all_accounts_on_this_device">為本設備上的所有帳戶設置</string> + <string name="presence_chat">免費聊天室</string> + <string name="presence_online">線上</string> + <string name="presence_away">離開</string> + <string name="presence_xa">離線</string> + <string name="presence_dnd">忙碌</string> + <string name="secure_password_generated">安全密碼已生成</string> + <string name="device_does_not_support_battery_op">該設備不支援禁用電池優化</string> + <string name="show_password">顯示密碼</string> + <string name="registration_please_wait">註冊失敗:請重試</string> + <string name="registration_password_too_weak">註冊失敗:密碼太弱</string> + <string name="create_conference">創建討論群組</string> + <string name="join_or_create_conference">加入創建討論群組</string> + <string name="conference_subject">主題</string> + <string name="choose_participants">選擇成員</string> + <string name="creating_conference">正在創建討論群組…</string> + <string name="invite_again">重新邀請</string> + <string name="gp_short">短</string> + <string name="gp_medium">中</string> + <string name="gp_long">長</string> + <string name="pref_broadcast_last_activity">廣播最後打開該應用的時間</string> + <string name="pref_broadcast_last_activity_summary">讓你的所有連絡人知道你使用Conversations的時間</string> + <string name="pref_privacy">隱私</string> + <string name="pref_theme_options">主題</string> + <string name="pref_theme_options_summary">選擇調色板</string> + <string name="pref_theme_light">明亮主題</string> + <string name="pref_theme_dark">黑暗主題</string> + <string name="pref_use_green_background">綠色背景</string> + <string name="pref_use_green_background_summary">接收到的消息使用綠色背景</string> + <string name="unable_to_connect_to_keychain">無法連接到 OpenKeychain</string> + <string name="this_device_is_no_longer_in_use">此設備不再使用</string> + <string name="type_pc">電腦</string> + <string name="type_phone">行動電話</string> + <string name="type_tablet">平板</string> + <string name="type_web">流覽器</string> + <string name="type_console">控制台</string> + <string name="payment_required">需要付款</string> + <string name="missing_internet_permission">缺少互聯網許可權</string> + <string name="me">我</string> + <string name="contact_asks_for_presence_subscription">連絡人請求線上訂閱</string> + <string name="allow">允許</string> + <string name="no_permission_to_access_x">沒有訪問 %s 的許可</string> + <string name="remote_server_not_found">找不到遠端伺服器</string> + <string name="unable_to_update_account">無法更新帳戶</string> + <string name="missing_presence_subscription_with_x">缺少與 %s 的線上訂閱。</string> + <string name="missing_keys_from_x">缺少來自 %s 的 OMEMO 金鑰。</string> + <string name="missing_omemo_keys">缺少 OMEMO 金鑰</string> + <string name="wrong_conference_configuration">這不是一個私密,非匿名的會議室。</string> + <string name="this_conference_has_no_members">這個會議室中沒有成員。</string> + <string name="report_jid_as_spammer">報告這個 JID 發送不想要的消息。</string> + <string name="pref_delete_omemo_identities">刪除 OMEMO 身份</string> + <string name="pref_delete_omemo_identities_summary">重新生成你的 OMEMO 金鑰。你的所有連絡人不得不對你進行再認證。請將此作為最後的辦法。</string> + <string name="delete_selected_keys">刪除選擇的金鑰</string> + <string name="error_publish_avatar_offline">你需要連接才能發佈頭像</string> + <string name="show_error_message">顯示出錯消息</string> + <string name="error_message">出錯消息</string> + <string name="data_saver_enabled">省流量模式已啟動</string> + <string name="data_saver_enabled_explained">您的作業系統禁止本程式在後臺運行時訪問互聯網。為了收到新消息提示,您需要在省流量模式下允許本程式不受限制地訪問互聯網。\n本程式仍會盡可能節省流量。</string> + <string name="device_does_not_support_data_saver">該設備不支援禁用省流量模式</string> </resources> diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml index 82f9db89..7b7d2c5a 100644 --- a/src/main/res/values/attrs.xml +++ b/src/main/res/values/attrs.xml @@ -47,6 +47,7 @@ <attr name="icon_settings" format="reference"/> <attr name="icon_share" format="reference"/> <attr name="icon_import_export" format="reference"/> + <attr name="icon_scan_qr_code" format="reference"/> <attr name="icon_notifications" format="reference"/> <attr name="icon_notifications_off" format="reference"/> diff --git a/src/main/res/values/ids.xml b/src/main/res/values/ids.xml new file mode 100644 index 00000000..142e4d66 --- /dev/null +++ b/src/main/res/values/ids.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <item type="id" name="TAG_ACCOUNT"/> + <item type="id" name="TAG_FINGERPRINT"/> + <item type="id" name="TAG_FINGERPRINT_STATUS"/> +</resources>
\ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 580b56c4..57599032 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -123,14 +123,14 @@ <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_summary">Let your contacts know when you have received and read their messages</string> <string name="pref_ui_options">UI</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="pref_grant_presence_updates_summary">Preemptively grant and request presence subscriptions for contacts you have created</string> <string name="subscriptions">Subscriptions</string> <string name="your_account">Your account</string> <string name="keys">Keys</string> @@ -363,6 +363,7 @@ <string name="check_x_filesize_on_host">Check %1$s size on %2$s</string> <string name="message_options">Message options</string> <string name="copy_text">Copy text</string> + <string name="select_text">Select text</string> <string name="copy_original_url">Copy original URL</string> <string name="send_again">Send again</string> <string name="file_url">File URL</string> @@ -370,8 +371,8 @@ <string name="url_copied_to_clipboard">URL copied to clipboard</string> <string name="message_copied_to_clipboard">Message copied to clipboard</string> <string name="image_transmission_failed">Image transmission failed</string> - <string name="scan_qr_code">Scan QR code</string> - <string name="show_qr_code">Show QR code</string> + <string name="scan_qr_code">Scan 2D Barcode</string> + <string name="show_qr_code">Show 2D Barcode</string> <string name="show_block_list">Show block list</string> <string name="account_details">Account details</string> <string name="verify_otr">Verify OTR</string> @@ -411,7 +412,7 @@ <string name="no_application_found_to_open_file">No application found to open file</string> <string name="could_not_verify_fingerprint">Could not verify fingerprint</string> <string name="manually_verify">Manually verify</string> - <string name="are_you_sure_verify_fingerprint">Are you sure that you want to verify your contacts OTR fingerprint?</string> + <string name="are_you_sure_verify_fingerprint">Are you sure that you want to verify your contact’s OTR fingerprint?</string> <string name="pref_show_dynamic_tags">Show dynamic tags</string> <string name="pref_show_dynamic_tags_summary">Display read-only tags underneath contacts</string> <string name="enable_notifications">Enable notifications</string> @@ -504,7 +505,7 @@ <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> + <string name="pref_chat_states_summary">Let your contacts know when you are writing messages to them</string> <string name="send_location">Send location</string> <string name="show_location">Show location</string> <string name="no_application_found_to_display_location">No application found to display location</string> @@ -693,6 +694,27 @@ <string name="pref_delete_omemo_identities_summary">Regenerate your OMEMO keys. All your contacts will have to verify you again. Use this only as a last resort.</string> <string name="delete_selected_keys">Delete selected keys</string> <string name="error_publish_avatar_offline">You need to be connected to publish your avatar.</string> + <string name="show_error_message">Show error message</string> + <string name="error_message">Error Message</string> + <string name="data_saver_enabled">Data saver enabled</string> + <string name="data_saver_enabled_explained">Your operating system is restricting Conversations from accessing the Internet when in background. To receive notifications of new messages you should allow Conversations unrestricted access when Data saver is on.\nConversations will still make an effort to save data when possible.</string> + <string name="device_does_not_support_data_saver">Your device does not support disabling Data saver for Conversations.</string> + <string name="error_unable_to_create_temporary_file">Unable to create temporary file</string> + <string name="this_device_has_been_verified">This device has been verified</string> + <string name="copy_fingerprint">Copy fingerprint</string> + <string name="all_omemo_keys_have_been_verified">All OMEMO keys have been verified</string> + <string name="barcode_does_not_contain_fingerprints_for_this_conversation">Barcode does not contain fingerprints for this conversation.</string> + <string name="verified_fingerprints">Verified fingerprints</string> + <string name="use_camera_icon_to_scan_barcode">Use the camera to scan a contact’s barcode</string> + <string name="please_wait_for_keys_to_be_fetched">Please wait for keys to be fetched</string> + <string name="share_as_barcode">Share as Barcode</string> + <string name="share_as_uri">Share as XMPP URI</string> + <string name="share_as_http">Share as HTTP link</string> + <string name="pref_blind_trust_before_verification">Blind Trust Before Verification</string> + <string name="pref_blind_trust_before_verification_summary">Automatically trust all new devices of contacts that haven’t been verified before, and prompt for manual confirmation each time a verified contact adds a new device.</string> + <string name="blindly_trusted_omemo_keys">Blindly trusted OMEMO keys</string> + <string name="not_trusted">Untrusted</string> + <string name="invalid_barcode">Invalid 2D barcode</string> <string name="pref_clean_cache_summary">Clean cache folder (used by Camera Application)</string> <string name="pref_clean_cache">Clean cache</string> <string name="pref_clean_private_storage">Clean private storage</string> diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml index f15822c9..69d4d39f 100644 --- a/src/main/res/values/themes.xml +++ b/src/main/res/values/themes.xml @@ -57,6 +57,7 @@ <item name="attr/icon_settings">@drawable/ic_action_settings</item> <item name="attr/icon_import_export">@drawable/ic_stat_communication_import_export</item> <item name="attr/icon_share">@drawable/ic_action_share</item> + <item name="attr/icon_scan_qr_code">@drawable/ic_action_camera</item> <item name="attr/icon_notifications">@drawable/ic_notifications_black54_24dp</item> <item name="attr/icon_notifications_off">@drawable/ic_notifications_off_black54_24dp</item> @@ -120,6 +121,7 @@ <item name="attr/icon_settings">@drawable/ic_action_settings_white</item> <item name="attr/icon_import_export">@drawable/ic_stat_communication_import_export</item> <item name="attr/icon_share">@drawable/ic_action_share</item> + <item name="attr/icon_scan_qr_code">@drawable/ic_action_camera</item> <item name="attr/icon_notifications">@drawable/ic_notifications_white80</item> <item name="attr/icon_notifications_off">@drawable/ic_notifications_off_white80</item> diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index 569876f4..751f3bec 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -166,6 +166,11 @@ <PreferenceCategory android:title="@string/pref_security_settings" android:key="security_options"> <CheckBoxPreference + android:defaultValue="true" + android:key="btbv" + android:title="@string/pref_blind_trust_before_verification" + android:summary="@string/pref_blind_trust_before_verification_summary"/> + <CheckBoxPreference android:defaultValue="false" android:key="dont_save_encrypted" android:summary="@string/pref_dont_save_encrypted_summary" @@ -263,7 +268,7 @@ android:title="@string/pref_use_indicate_received"/> <CheckBoxPreference android:defaultValue="false" - android:key="keep_foreground_service" + android:key="enable_foreground_service" android:summary="@string/pref_keep_foreground_service_summary" android:title="@string/pref_keep_foreground_service"/> <Preference |