diff --git a/CHANGELOG.md b/CHANGELOG.md index ce7f4430e..5cf05cfaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### Version 2.8.8 +* Fixed notifications not showing up under certain conditions +* Fixed compatibility issues and crashes related to A/V calls + ### Version 2.8.7 * Show help button if A/V call fails diff --git a/art/render.rb b/art/render.rb index 863a5cca3..7fb46d138 100755 --- a/art/render.rb +++ b/art/render.rb @@ -119,7 +119,7 @@ images.each do |source_filename, settings| else path = "../src/#{output_parts[0]}/res/drawable-#{resolution}/#{output_parts[1]}.png" end - execute_cmd "#{inkscape} -f #{source_filename} -z -C -w #{width} -h #{height} -e #{path}" + execute_cmd "#{inkscape} #{source_filename} -C -w #{width} -h #{height} -o #{path}" top = [] right = [] diff --git a/build.gradle b/build.gradle index a6c770309..1703a7854 100644 --- a/build.gradle +++ b/build.gradle @@ -96,8 +96,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 25 - versionCode 393 - versionName "2.8.7" + versionCode 394 + versionName "2.8.8" archivesBaseName += "-$versionName" applicationId "eu.sum7.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/393.txt b/fastlane/metadata/android/en-US/changelogs/393.txt new file mode 100644 index 000000000..82250ee87 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/393.txt @@ -0,0 +1,3 @@ +* Show help button if A/V call fails +* Fixed some annoying crashes +* Fixed Jingle connections (file transfer + calls) with bare JIDs diff --git a/metadata/en-US/changelogs/394.txt b/metadata/en-US/changelogs/394.txt index 18107a020..1923a9ff9 100644 --- a/metadata/en-US/changelogs/394.txt +++ b/metadata/en-US/changelogs/394.txt @@ -1,3 +1,2 @@ -• Show help button if A/V call fails -• Fixed some annoying crashes -• Fixed Jingle connections (file transfer + calls) with bare JIDs +• Fixed notifications not showing up under certain conditions +• Fixed compatibility issues and crashes related to A/V calls diff --git a/src/conversations/AndroidManifest.xml b/src/conversations/AndroidManifest.xml index 2100d9719..90b78ed4c 100644 --- a/src/conversations/AndroidManifest.xml +++ b/src/conversations/AndroidManifest.xml @@ -38,6 +38,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java b/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java new file mode 100644 index 000000000..702d45e23 --- /dev/null +++ b/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java @@ -0,0 +1,51 @@ +package eu.siacs.conversations.entities; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import com.google.gson.annotations.SerializedName; + +import eu.siacs.conversations.xmpp.Jid; + +public class AccountConfiguration { + + private static final Gson GSON = new GsonBuilder().create(); + + public Protocol protocol; + public String address; + public String password; + + public Jid getJid() { + return Jid.ofEscaped(address); + } + + public static AccountConfiguration parse(final String input) { + final AccountConfiguration c; + try { + c = GSON.fromJson(input, AccountConfiguration.class); + } catch (JsonSyntaxException e) { + throw new IllegalArgumentException("Not a valid JSON string", e); + } + Preconditions.checkArgument( + c.protocol == Protocol.XMPP, + "Protocol must be XMPP" + ); + Preconditions.checkArgument( + c.address != null && c.getJid().isBareJid() && !c.getJid().isDomainJid(), + "Invalid XMPP address" + ); + Preconditions.checkArgument( + c.password != null && c.password.length() > 0, + "No password specified" + ); + return c; + } + + public enum Protocol { + @SerializedName("xmpp") XMPP, + } + +} + diff --git a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java index d2b2e58a1..03e41a1bf 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java @@ -6,6 +6,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.databinding.DataBindingUtil; +import android.net.Uri; import android.os.Bundle; import android.security.KeyChain; import android.security.KeyChainAliasCallback; @@ -25,6 +26,7 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityWelcomeBinding; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.InstallReferrerUtils; import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.XmppUri; @@ -46,35 +48,37 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi activity.overridePendingTransition(0, 0); } - public void onInstallReferrerDiscovered(final String referrer) { + public void onInstallReferrerDiscovered(final Uri referrer) { Log.d(Config.LOGTAG, "welcome activity: on install referrer discovered " + referrer); - if (referrer != null) { + if ("xmpp".equalsIgnoreCase(referrer.getScheme())) { final XmppUri xmppUri = new XmppUri(referrer); runOnUiThread(() -> processXmppUri(xmppUri)); + } else { + Log.i(Config.LOGTAG, "install referrer was not an XMPP uri"); } } - private boolean processXmppUri(final XmppUri xmppUri) { - if (xmppUri.isValidJid()) { - final String preauth = xmppUri.getParameter("preauth"); - final Jid jid = xmppUri.getJid(); - final Intent intent; - if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) { - intent = SignupUtils.getTokenRegistrationIntent(this, jid, preauth); - } else if (xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter("ibr"))) { - intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preauth); - intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); - } else { - intent = null; - } - if (intent != null) { - startActivity(intent); - finish(); - return true; - } - this.inviteUri = xmppUri; + private void processXmppUri(final XmppUri xmppUri) { + if (!xmppUri.isValidJid()) { + return; } - return false; + final String preAuth = xmppUri.getParameter(XmppUri.PARAMETER_PRE_AUTH); + final Jid jid = xmppUri.getJid(); + final Intent intent; + if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) { + intent = SignupUtils.getTokenRegistrationIntent(this, jid, preAuth); + } else if (xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter(XmppUri.PARAMETER_IBR))) { + intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preAuth); + intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); + } else { + intent = null; + } + if (intent != null) { + startActivity(intent); + finish(); + return; + } + this.inviteUri = xmppUri; } @Override @@ -143,10 +147,12 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.welcome_menu, menu); final MenuItem scan = menu.findItem(R.id.action_scan_qr_code); - scan.setVisible(getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)); + scan.setVisible(Compatibility.hasFeatureCamera(this)); return super.onCreateOptionsMenu(menu); } + + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -156,7 +162,7 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi } break; case R.id.action_scan_qr_code: - UriHandlerActivity.scan(this); + UriHandlerActivity.scan(this, true); break; case R.id.action_add_account_with_cert: addAccountFromKey(); @@ -183,7 +189,7 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi @Override public void onAccountCreated(final Account account) { final Intent intent = new Intent(this, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().asBareJid().toString()); + intent.putExtra("jid", account.getJid().asBareJid().toEscapedString()); intent.putExtra("init", true); addInviteUri(intent); startActivity(intent); diff --git a/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java b/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java new file mode 100644 index 000000000..593291d95 --- /dev/null +++ b/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java @@ -0,0 +1,43 @@ +package eu.siacs.conversations.utils; + +import android.app.Activity; +import android.content.Intent; +import android.widget.Toast; + +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.AccountConfiguration; +import eu.siacs.conversations.persistance.DatabaseBackend; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.EditAccountActivity; +import eu.siacs.conversations.xmpp.Jid; + +public class ProvisioningUtils { + + public static void provision(final Activity activity, final String json) { + final AccountConfiguration accountConfiguration; + try { + accountConfiguration = AccountConfiguration.parse(json); + } catch (final IllegalArgumentException e) { + Toast.makeText(activity, R.string.improperly_formatted_provisioning, Toast.LENGTH_LONG).show(); + return; + } + final Jid jid = accountConfiguration.getJid(); + final List accounts = DatabaseBackend.getInstance(activity).getAccountJids(true); + if (accounts.contains(jid)) { + Toast.makeText(activity, R.string.account_already_exists, Toast.LENGTH_LONG).show(); + return; + } + final Intent serviceIntent = new Intent(activity, XmppConnectionService.class); + serviceIntent.setAction(XmppConnectionService.ACTION_PROVISION_ACCOUNT); + serviceIntent.putExtra("address", jid.asBareJid().toEscapedString()); + serviceIntent.putExtra("password", accountConfiguration.password); + Compatibility.startService(activity, serviceIntent); + final Intent intent = new Intent(activity, EditAccountActivity.class); + intent.putExtra("jid", jid.asBareJid().toEscapedString()); + intent.putExtra("init", true); + activity.startActivity(intent); + } + +} diff --git a/src/conversations/res/values-ru/strings.xml b/src/conversations/res/values-ru/strings.xml index 1b33459e0..55b9679c6 100644 --- a/src/conversations/res/values-ru/strings.xml +++ b/src/conversations/res/values-ru/strings.xml @@ -3,4 +3,6 @@ Выберите своего XMPP-провайдера Использовать chat.sum7.eu Создать новый аккаунт - + У вас есть аккаунт XMPP? Если вы использовали Conversations или другой XMPP-клиент в прошлом, то скорее всего, он у вас есть. Если у вас нет аккаунта, вы можете создать его прямо сейчас.\nНекоторые провайдеры электронной почты также регистрируют аккаунты XMPP. + XMPP - это независимая сеть обмена сообщениями. Conversations позволяет вам подключиться к любому XMPP-серверу на ваш выбор.\nЕсли у вас нет сервера, предлагаем вам зарегистрировать аккаунт на chat.sum7.eu, сервер, специально предназначеный для работы с приложением Conv6sations. + diff --git a/src/conversations/res/values/strings.xml b/src/conversations/res/values/strings.xml index 5f7ede8dd..23971f875 100644 --- a/src/conversations/res/values/strings.xml +++ b/src/conversations/res/values/strings.xml @@ -8,4 +8,5 @@ You have been invited to %1$s. We will guide you through the process of creating an account.\nWhen picking %1$s as a provider you will be able to communicate with users of other providers by giving them your full XMPP address. You have been invited to %1$s. A username has already been picked for you. We will guide you through the process of creating an account.\nYou will be able to communicate with users of other providers by giving them your full XMPP address. Your server invitation + Improperly formatted provisioning code diff --git a/src/conversationsPlaystore/java/eu/siacs/conversations/utils/InstallReferrerUtils.java b/src/conversationsPlaystore/java/eu/siacs/conversations/utils/InstallReferrerUtils.java index aac9b9f4a..360011bea 100644 --- a/src/conversationsPlaystore/java/eu/siacs/conversations/utils/InstallReferrerUtils.java +++ b/src/conversationsPlaystore/java/eu/siacs/conversations/utils/InstallReferrerUtils.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.utils; import android.app.Activity; import android.content.SharedPreferences; +import android.net.Uri; import android.os.RemoteException; import android.preference.PreferenceManager; import android.util.Log; @@ -9,6 +10,7 @@ import android.util.Log; import com.android.installreferrer.api.InstallReferrerClient; import com.android.installreferrer.api.InstallReferrerStateListener; import com.android.installreferrer.api.ReferrerDetails; +import com.google.common.base.Strings; import eu.siacs.conversations.Config; import eu.siacs.conversations.ui.WelcomeActivity; @@ -49,8 +51,11 @@ public class InstallReferrerUtils implements InstallReferrerStateListener { try { final ReferrerDetails referrerDetails = installReferrerClient.getInstallReferrer(); final String referrer = referrerDetails.getInstallReferrer(); - welcomeActivity.onInstallReferrerDiscovered(referrer); - } catch (final RemoteException e) { + if (Strings.isNullOrEmpty(referrer)) { + return; + } + welcomeActivity.onInstallReferrerDiscovered(Uri.parse(referrer)); + } catch (final RemoteException | IllegalArgumentException e) { Log.d(Config.LOGTAG, "unable to get install referrer", e); } } else { diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 83e49b9a5..d96260957 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -7,6 +7,7 @@ import android.support.annotation.Nullable; import android.text.TextUtils; import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Lists; import org.json.JSONArray; import org.json.JSONException; @@ -169,6 +170,22 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl return first; } + public String findMostRecentRemoteDisplayableId() { + final boolean multi = mode == Conversation.MODE_MULTI; + synchronized (this.messages) { + for(final Message message : Lists.reverse(this.messages)) { + if (message.getStatus() == Message.STATUS_RECEIVED) { + final String serverMsgId = message.getServerMsgId(); + if (serverMsgId != null && multi) { + return serverMsgId; + } + return message.getRemoteMsgId(); + } + } + } + return null; + } + public Message findUnsentMessageWithUuid(String uuid) { synchronized (this.messages) { for (final Message message : this.messages) { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 991fea547..7622d3071 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -837,9 +837,9 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece if (Namespace.JINGLE_MESSAGE.equals(child.getNamespace()) && JINGLE_MESSAGE_ELEMENT_NAMES.contains(child.getName())) { final String action = child.getName(); final String sessionId = child.getAttribute("id"); - if (sessionId == null) { - break; - } + if (sessionId == null) { + break; + } if (query == null) { if (serverMsgId == null) { serverMsgId = extractStanzaId(account, packet); @@ -952,7 +952,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece final String id = displayed.getAttribute("id"); final Jid sender = InvalidJid.getNullForInvalid(displayed.getAttributeAsJid("sender")); if (packet.fromAccount(account) && !selfAddressed) { - dismissNotification(account, counterpart, query); + dismissNotification(account, counterpart, query, id); if (query == null) { activateGracePeriod(account); } @@ -993,7 +993,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece message = message.prev(); } if (displayedMessage != null && selfAddressed) { - dismissNotification(account, counterpart, query); + dismissNotification(account, counterpart, query, id); } } } @@ -1018,10 +1018,15 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } } - private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query) { - Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid()); + private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query, final String id) { + final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid()); if (conversation != null && (query == null || query.isCatchup())) { - mXmppConnectionService.markRead(conversation); //TODO only mark messages read that are older than timestamp + final String displayableId = conversation.findMostRecentRemoteDisplayableId(); + if (displayableId != null && displayableId.equals(id)) { + mXmppConnectionService.markRead(conversation); + } else { + Log.w(Config.LOGTAG, account.getJid().asBareJid() + ": received dismissing display marker that did not match our last id in that conversation"); + } } } diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 7cadb1566..c53dbd7d5 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -602,9 +602,6 @@ public class NotificationService { } catch (SecurityException e) { Log.d(Config.LOGTAG, "unable to use custom notification sound " + uri.toString()); } - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mBuilder.setCategory(Notification.CATEGORY_MESSAGE); - } mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH); setNotificationColor(mBuilder); mBuilder.setLights(LED_COLOR, 2000, 3000); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 48030e7f3..812ca3716 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -170,6 +170,7 @@ public class XmppConnectionService extends Service { public static final String ACTION_FCM_MESSAGE_RECEIVED = "fcm_message_received"; public static final String ACTION_DISMISS_CALL = "dismiss_call"; public static final String ACTION_END_CALL = "end_call"; + public static final String ACTION_PROVISION_ACCOUNT = "provision_account"; private static final String ACTION_POST_CONNECTIVITY_CHANGE = "eu.siacs.conversations.POST_CONNECTIVITY_CHANGE"; private static final String SETTING_LAST_ACTIVITY_TS = "last_activity_timestamp"; @@ -659,6 +660,15 @@ public class XmppConnectionService extends Service { mJingleConnectionManager.endRtpSession(sessionId); } break; + case ACTION_PROVISION_ACCOUNT: { + final String address = intent.getStringExtra("address"); + final String password = intent.getStringExtra("password"); + if (QuickConversationsService.isQuicksy() || Strings.isNullOrEmpty(address) || Strings.isNullOrEmpty(password)) { + break; + } + provisionAccount(address, password); + break; + } case ACTION_DISMISS_ERROR_NOTIFICATIONS: dismissErrorNotifications(); break; @@ -2180,6 +2190,14 @@ public class XmppConnectionService extends Service { } } + private void provisionAccount(final String address, final String password) { + final Jid jid = Jid.ofEscaped(address); + final Account account = new Account(jid, password); + account.setOption(Account.OPTION_DISABLED, true); + Log.d(Config.LOGTAG,jid.asBareJid().toEscapedString()+": provisioning account"); + createAccount(account); + } + public void createAccountFromKey(final String alias, final OnAccountCreated callback) { new Thread(() -> { try { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 6ff2135aa..034ac1485 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -900,7 +900,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke private static boolean anyNeedsExternalStoragePermission(final Collection attachments) { - for(final Attachment attachment : attachments) { + for (final Attachment attachment : attachments) { if (attachment.getType() != Attachment.Type.LOCATION) { return true; } @@ -1346,7 +1346,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke final Contact contact = conversation.getContact(); if (contact.getPresences().anySupport(Namespace.JINGLE_MESSAGE)) { - triggerRtpSession(contact.getAccount(),contact.getJid().asBareJid(),action); + triggerRtpSession(contact.getAccount(), contact.getJid().asBareJid(), action); } else { final RtpCapability.Capability capability; if (action.equals(RtpSessionActivity.ACTION_MAKE_VIDEO_CALL)) { @@ -1436,6 +1436,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } public void attachFile(final int attachmentChoice) { + attachFile(attachmentChoice, true); + } + + public void attachFile(final int attachmentChoice, final boolean updateRecentlyUsed) { if (attachmentChoice == ATTACHMENT_CHOICE_RECORD_VOICE) { if (!hasPermissions(attachmentChoice, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO)) { return; @@ -1449,12 +1453,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return; } } - try { - activity.getPreferences().edit() - .putString(RECENTLY_USED_QUICK_ACTION, SendButtonAction.of(attachmentChoice).toString()) - .apply(); - } catch (IllegalArgumentException e) { - //just do not save + if (updateRecentlyUsed) { + storeRecentlyUsedQuickAction(attachmentChoice); } final int encryption = conversation.getNextEncryption(); final int mode = conversation.getMode(); @@ -1502,6 +1502,16 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } + private void storeRecentlyUsedQuickAction(final int attachmentChoice) { + try { + activity.getPreferences().edit() + .putString(RECENTLY_USED_QUICK_ACTION, SendButtonAction.of(attachmentChoice).toString()) + .apply(); + } catch (IllegalArgumentException e) { + //just do not save + } + } + @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (grantResults.length > 0) { @@ -2134,7 +2144,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return this.binding != null && scrolledToBottom(this.binding.messagesView); } - private void processExtras(Bundle extras) { + private void processExtras(final Bundle extras) { final String downloadUuid = extras.getString(ConversationsActivity.EXTRA_DOWNLOAD_UUID); final String text = extras.getString(Intent.EXTRA_TEXT); final String nick = extras.getString(ConversationsActivity.EXTRA_NICK); @@ -2180,7 +2190,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } if (ConversationsActivity.POST_ACTION_RECORD_VOICE.equals(postInitAction)) { - attachFile(ATTACHMENT_CHOICE_RECORD_VOICE); + attachFile(ATTACHMENT_CHOICE_RECORD_VOICE, false); return; } final Message message = downloadUuid == null ? null : conversation.findMessageWithFileAndUuid(downloadUuid); @@ -2189,7 +2199,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } - private List extractUris(Bundle extras) { + private List extractUris(final Bundle extras) { final List uris = extras.getParcelableArrayList(Intent.EXTRA_STREAM); if (uris != null) { return uris; @@ -2202,7 +2212,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } - private List cleanUris(List uris) { + private List cleanUris(final List uris) { Iterator iterator = uris.iterator(); while (iterator.hasNext()) { final Uri uri = iterator.next(); @@ -2878,13 +2888,13 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke private void clearPending() { if (postponedActivityResult.clear()) { Log.e(Config.LOGTAG, "cleared pending intent with unhandled result left"); + if (pendingTakePhotoUri.clear()) { + Log.e(Config.LOGTAG, "cleared pending photo uri"); + } } if (pendingScrollState.clear()) { Log.e(Config.LOGTAG, "cleared scroll state"); } - if (pendingTakePhotoUri.clear()) { - Log.e(Config.LOGTAG, "cleared pending photo uri"); - } if (pendingConversationsUuid.clear()) { Log.e(Config.LOGTAG, "cleared pending conversations uuid"); } diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 23a743eda..5db8dfc6d 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -276,11 +276,15 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe private void releaseProximityWakeLock() { if (this.mProximityWakeLock != null && mProximityWakeLock.isHeld()) { Log.d(Config.LOGTAG, "releasing proximity wake lock"); - this.mProximityWakeLock.release(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + this.mProximityWakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY); + } else { + this.mProximityWakeLock.release(); + } this.mProximityWakeLock = null; } } - + private void putProximityWakeLockInProperState(final AppRTCAudioManager.AudioDevice audioDevice) { if (audioDevice == AppRTCAudioManager.AudioDevice.EARPIECE) { acquireProximityWakeLock(); @@ -402,10 +406,6 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe final JingleRtpConnection jingleRtpConnection = weakReference == null ? null : weakReference.get(); if (jingleRtpConnection != null) { releaseVideoTracks(jingleRtpConnection); - } else if (!isChangingConfigurations()) { - if (xmppConnectionService != null) { - retractSessionProposal(); - } } releaseProximityWakeLock(); super.onStop(); @@ -424,17 +424,20 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe @Override public void onBackPressed() { - endCall(); super.onBackPressed(); + endCall(); } @Override public void onUserLeaveHint() { + super.onUserLeaveHint(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && deviceSupportsPictureInPicture()) { if (shouldBePictureInPicture()) { startPictureInPicture(); + return; } } + retractSessionProposal(); } @RequiresApi(api = Build.VERSION_CODES.O) @@ -445,7 +448,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe .setAspectRatio(new Rational(10, 16)) .build() ); - } catch (IllegalStateException e) { + } catch (final IllegalStateException e) { //this sometimes happens on Samsung phones (possibly when Knox is enabled) Log.w(Config.LOGTAG, "unable to enter picture in picture mode", e); } @@ -467,7 +470,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe RtpEndUserState.CONNECTING, RtpEndUserState.CONNECTED ).contains(rtpConnection.getEndUserState()); - } catch (IllegalStateException e) { + } catch (final IllegalStateException e) { return false; } } diff --git a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java index ae63f493a..fdebd6a56 100644 --- a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java @@ -11,12 +11,16 @@ import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.widget.Toast; +import com.google.common.base.Strings; + import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import eu.siacs.conversations.R; import eu.siacs.conversations.persistance.DatabaseBackend; +import eu.siacs.conversations.services.QuickConversationsService; +import eu.siacs.conversations.utils.ProvisioningUtils; import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; @@ -24,29 +28,45 @@ import eu.siacs.conversations.xmpp.Jid; public class UriHandlerActivity extends AppCompatActivity { public static final String ACTION_SCAN_QR_CODE = "scan_qr_code"; + private static final String EXTRA_ALLOW_PROVISIONING = "extra_allow_provisioning"; private static final int REQUEST_SCAN_QR_CODE = 0x1234; private static final int REQUEST_CAMERA_PERMISSIONS_TO_SCAN = 0x6789; - private static final Pattern VCARD_XMPP_PATTERN = Pattern.compile("\nIMPP([^:]*):(xmpp:.+)\n"); + private static final int REQUEST_CAMERA_PERMISSIONS_TO_SCAN_AND_PROVISION = 0x6790; + private static final Pattern V_CARD_XMPP_PATTERN = Pattern.compile("\nIMPP([^:]*):(xmpp:.+)\n"); private boolean handled = false; - public static void scan(Activity activity) { + public static void scan(final Activity activity) { + scan(activity, false); + } + + public static void scan(final Activity activity, final boolean provisioning) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { - Intent intent = new Intent(activity, UriHandlerActivity.class); + final Intent intent = new Intent(activity, UriHandlerActivity.class); intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE); + if (provisioning) { + intent.putExtra(EXTRA_ALLOW_PROVISIONING, true); + } intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); activity.startActivity(intent); } else { - activity.requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSIONS_TO_SCAN); + activity.requestPermissions( + new String[]{Manifest.permission.CAMERA}, + provisioning ? REQUEST_CAMERA_PERMISSIONS_TO_SCAN_AND_PROVISION : REQUEST_CAMERA_PERMISSIONS_TO_SCAN + ); } } public static void onRequestPermissionResult(Activity activity, int requestCode, int[] grantResults) { - if (requestCode != REQUEST_CAMERA_PERMISSIONS_TO_SCAN) { + if (requestCode != REQUEST_CAMERA_PERMISSIONS_TO_SCAN && requestCode != REQUEST_CAMERA_PERMISSIONS_TO_SCAN_AND_PROVISION) { return; } if (grantResults.length > 0) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - scan(activity); + if (requestCode == REQUEST_CAMERA_PERMISSIONS_TO_SCAN_AND_PROVISION) { + scan(activity, true); + } else { + scan(activity); + } } else { Toast.makeText(activity, R.string.qr_code_scanner_needs_access_to_camera, Toast.LENGTH_SHORT).show(); } @@ -88,19 +108,19 @@ public class UriHandlerActivity extends AppCompatActivity { final List accounts = DatabaseBackend.getInstance(this).getAccountJids(true); if (SignupUtils.isSupportTokenRegistry() && xmppUri.isValidJid()) { - final String preauth = xmppUri.getParameter("preauth"); + final String preAuth = xmppUri.getParameter(XmppUri.PARAMETER_PRE_AUTH); final Jid jid = xmppUri.getJid(); if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) { if (jid.getEscapedLocal() != null && accounts.contains(jid.asBareJid())) { Toast.makeText(this, R.string.account_already_exists, Toast.LENGTH_LONG).show(); return; } - intent = SignupUtils.getTokenRegistrationIntent(this, jid, preauth); + intent = SignupUtils.getTokenRegistrationIntent(this, jid, preAuth); startActivity(intent); return; } - if (xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter("ibr"))) { - intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preauth); + if (xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter(XmppUri.PARAMETER_IBR))) { + intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preAuth); intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); startActivity(intent); return; @@ -194,22 +214,38 @@ public class UriHandlerActivity extends AppCompatActivity { finish(); } + private boolean allowProvisioning() { + final Intent launchIntent = getIntent(); + return launchIntent != null && launchIntent.getBooleanExtra(EXTRA_ALLOW_PROVISIONING, false); + } + @Override - public void onActivityResult(int requestCode, int resultCode, Intent intent) { + public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { super.onActivityResult(requestCode, requestCode, intent); if (requestCode == REQUEST_SCAN_QR_CODE && resultCode == RESULT_OK) { - String result = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT); - if (result != null) { - if (result.startsWith("BEGIN:VCARD\n")) { - Matcher matcher = VCARD_XMPP_PATTERN.matcher(result); - if (matcher.find()) { - result = matcher.group(2); - } - } - Uri uri = Uri.parse(result); - handleUri(uri, true); + final String result = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT); + if (Strings.isNullOrEmpty(result)) { + finish(); + return; } + if (result.startsWith("BEGIN:VCARD\n")) { + final Matcher matcher = V_CARD_XMPP_PATTERN.matcher(result); + if (matcher.find()) { + handleUri(Uri.parse(matcher.group(2)), true); + } + finish(); + return; + } else if (QuickConversationsService.isConversations() && looksLikeJsonObject(result) && allowProvisioning()) { + ProvisioningUtils.provision(this, result); + finish(); + return; + } + handleUri(Uri.parse(result), true); } finish(); } + + private static boolean looksLikeJsonObject(final String input) { + return input.charAt(0) == '{' && input.charAt(input.length() - 1) == '}'; + } } \ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/utils/Compatibility.java b/src/main/java/eu/siacs/conversations/utils/Compatibility.java index 13e38e487..b03ad5454 100644 --- a/src/main/java/eu/siacs/conversations/utils/Compatibility.java +++ b/src/main/java/eu/siacs/conversations/utils/Compatibility.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.utils; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -139,4 +140,15 @@ public class Compatibility { Log.d(Config.LOGTAG, context.getClass().getSimpleName() + " was unable to start service"); } } + + + @SuppressLint("UnsupportedChromeOsCameraSystemFeature") + public static boolean hasFeatureCamera(final Context context) { + final PackageManager packageManager = context.getPackageManager(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY); + } else { + return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA); + } + } } diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java index 9b27a123a..5db19ad05 100644 --- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java +++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java @@ -22,6 +22,8 @@ public class XmppUri { public static final String ACTION_MESSAGE = "message"; public static final String ACTION_REGISTER = "register"; public static final String ACTION_ROSTER = "roster"; + public static final String PARAMETER_PRE_AUTH = "preauth"; + public static final String PARAMETER_IBR = "ibr"; private static final String OMEMO_URI_PARAM = "omemo-sid-"; protected Uri uri; protected String jid; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index e45b79997..28f7c738b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -39,6 +39,7 @@ import eu.siacs.conversations.services.AppRTCAudioManager; import eu.siacs.conversations.utils.IP; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; @@ -47,7 +48,6 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; -import eu.siacs.conversations.xmpp.Jid; public class JingleRtpConnection extends AbstractJingleConnection implements WebRTCWrapper.EventCallback { @@ -120,7 +120,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private final WebRTCWrapper webRTCWrapper = new WebRTCWrapper(this); - private final ArrayDeque pendingIceCandidates = new ArrayDeque<>(); + private final ArrayDeque>> pendingIceCandidates = new ArrayDeque<>(); private final Message message; private State state = State.NULL; private StateTransitionException stateTransitionException; @@ -237,13 +237,12 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents; ignoring", e); return; } - final RtpContentMap rtpContentMap = isInitiator() ? this.responderRtpContentMap : this.initiatorRtpContentMap; - final Group originalGroup = rtpContentMap != null ? rtpContentMap.group : null; - final List identificationTags = originalGroup == null ? Collections.emptyList() : originalGroup.getIdentificationTags(); - if (identificationTags.size() == 0) { - Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": no identification tags found in initial offer. we won't be able to calculate mLineIndices"); + final Set> candidates = contentMap.contents.entrySet(); + if (this.state == State.SESSION_ACCEPTED) { + processCandidates(candidates); + } else { + pendingIceCandidates.push(candidates); } - receiveCandidates(identificationTags, contentMap.contents.entrySet()); } else { if (isTerminated()) { respondOk(jinglePacket); @@ -255,7 +254,17 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } } - private void receiveCandidates(final List identificationTags, final Set> contents) { + private void processCandidates(final Set> contents) { + final RtpContentMap rtpContentMap = isInitiator() ? this.responderRtpContentMap : this.initiatorRtpContentMap; + final Group originalGroup = rtpContentMap.group; + final List identificationTags = originalGroup == null ? rtpContentMap.getNames() : originalGroup.getIdentificationTags(); + if (identificationTags.size() == 0) { + Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": no identification tags found in initial offer. we won't be able to calculate mLineIndices"); + } + processCandidates(identificationTags, contents); + } + + private void processCandidates(final List indices, final Set> contents) { for (final Map.Entry content : contents) { final String ufrag = content.getValue().transport.getAttribute("ufrag"); for (final IceUdpTransportInfo.Candidate candidate : content.getValue().transport.getCandidates()) { @@ -267,15 +276,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web continue; } final String sdpMid = content.getKey(); - final int mLineIndex = identificationTags.indexOf(sdpMid); + final int mLineIndex = indices.indexOf(sdpMid); final IceCandidate iceCandidate = new IceCandidate(sdpMid, mLineIndex, sdp); - if (isInState(State.SESSION_ACCEPTED)) { - Log.d(Config.LOGTAG, "received candidate: " + iceCandidate); - this.webRTCWrapper.addIceCandidate(iceCandidate); - } else { - this.pendingIceCandidates.offer(iceCandidate); - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": put ICE candidate on backlog"); - } + Log.d(Config.LOGTAG, "received candidate: " + iceCandidate); + this.webRTCWrapper.addIceCandidate(iceCandidate); } } } @@ -318,8 +322,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } if (transition(target, () -> this.initiatorRtpContentMap = contentMap)) { respondOk(jinglePacket); - final List identificationTags = contentMap.group == null ? Collections.emptyList() : contentMap.group.getIdentificationTags(); - receiveCandidates(identificationTags, contentMap.contents.entrySet()); + pendingIceCandidates.push(contentMap.contents.entrySet()); if (target == State.SESSION_INITIALIZED_PRE_APPROVED) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": automatically accepting session-initiate"); sendSessionAccept(); @@ -364,8 +367,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web if (transition(State.SESSION_ACCEPTED)) { respondOk(jinglePacket); receiveSessionAccept(contentMap); - final List identificationTags = contentMap.group == null ? Collections.emptyList() : contentMap.group.getIdentificationTags(); - receiveCandidates(identificationTags, contentMap.contents.entrySet()); + final List identificationTags = contentMap.group == null ? contentMap.getNames() : contentMap.group.getIdentificationTags(); + processCandidates(identificationTags, contentMap.contents.entrySet()); } else { Log.d(Config.LOGTAG, String.format("%s: received session-accept while in state %s", id.account.getJid().asBareJid(), state)); respondOk(jinglePacket); @@ -451,9 +454,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web private void addIceCandidatesFromBlackLog() { while (!this.pendingIceCandidates.isEmpty()) { - final IceCandidate iceCandidate = this.pendingIceCandidates.poll(); - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": added ICE candidate from back log " + iceCandidate); - this.webRTCWrapper.addIceCandidate(iceCandidate); + processCandidates(this.pendingIceCandidates.poll()); + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": added candidates from back log"); } } @@ -461,7 +463,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web this.responderRtpContentMap = rtpContentMap; this.transitionOrThrow(State.SESSION_ACCEPTED); final JinglePacket sessionAccept = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId); - Log.d(Config.LOGTAG, sessionAccept.toString()); send(sessionAccept); } @@ -889,6 +890,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } public synchronized void rejectCall() { + if (isTerminated()) { + Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": received rejectCall() when session has already been terminated. nothing to do"); + return; + } switch (this.state) { case PROPOSED: rejectCallFromProposed(); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java index 9150d393e..11ad513b7 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java @@ -6,6 +6,7 @@ import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; @@ -13,6 +14,7 @@ import com.google.common.collect.Sets; import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import java.util.List; import java.util.Map; import java.util.Set; @@ -23,7 +25,6 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; -import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; public class RtpContentMap { @@ -59,6 +60,10 @@ public class RtpContentMap { })); } + public List getNames() { + return ImmutableList.copyOf(contents.keySet()); + } + void requireContentDescriptions() { if (this.contents.size() == 0) { throw new IllegalStateException("No contents available"); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java index aa33ae41c..af7213220 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java @@ -168,8 +168,10 @@ public class SessionDescription { } formatBuilder.add(payloadType.getIntId()); mediaAttributes.put("rtpmap", payloadType.toSdpAttribute()); - List parameters = payloadType.getParameters(); - if (parameters.size() > 0) { + final List parameters = payloadType.getParameters(); + if (parameters.size() == 1) { + mediaAttributes.put("fmtp", RtpDescription.Parameter.toSdpString(id, parameters.get(0))); + } else if (parameters.size() > 0) { mediaAttributes.put("fmtp", RtpDescription.Parameter.toSdpString(id, parameters)); } for (RtpDescription.FeedbackNegotiation feedbackNegotiation : payloadType.getFeedbackNegotiations()) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 4e5fc2641..ff7e6a677 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -214,9 +214,13 @@ public class WebRTCWrapper { PeerConnectionFactory.InitializationOptions.builder(service).createInitializationOptions() ); } catch (final UnsatisfiedLinkError e) { - throw new InitializationException(e); + throw new InitializationException("Unable to initialize PeerConnectionFactory", e); + } + try { + this.eglBase = EglBase.create(); + } catch (final RuntimeException e) { + throw new InitializationException("Unable to create EGL base", e); } - this.eglBase = EglBase.create(); this.context = service; this.toneManager = service.getJingleConnectionManager().toneManager; mainHandler.post(() -> { @@ -589,8 +593,8 @@ public class WebRTCWrapper { static class InitializationException extends Exception { - private InitializationException(final Throwable throwable) { - super(throwable); + private InitializationException(final String message, final Throwable throwable) { + super(message, throwable); } private InitializationException(final String message) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/RtpDescription.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/RtpDescription.java index d40363b49..9a1630f80 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/RtpDescription.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/RtpDescription.java @@ -3,6 +3,7 @@ package eu.siacs.conversations.xmpp.jingle.stanzas; import android.util.Pair; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; @@ -369,7 +370,7 @@ public class RtpDescription extends GenericDescription { final StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(id).append(' '); for (int i = 0; i < parameters.size(); ++i) { - Parameter p = parameters.get(i); + final Parameter p = parameters.get(i); final String name = p.getParameterName(); Preconditions.checkArgument(name != null, String.format("parameter for %s must have a name", id)); SessionDescription.checkNoWhitespace(name, String.format("parameter names for %s must not contain whitespaces", id)); @@ -386,11 +387,23 @@ public class RtpDescription extends GenericDescription { return stringBuilder.toString(); } - public static Pair> ofSdpString(final String sdp) { + public static String toSdpString(final String id, final Parameter parameter) { + final String name = parameter.getParameterName(); + final String value = parameter.getParameterValue(); + Preconditions.checkArgument(value != null, String.format("parameter for %s must have a value", id)); + SessionDescription.checkNoWhitespace(value, String.format("parameter values for %s must not contain whitespaces", id)); + if (Strings.isNullOrEmpty(name)) { + return String.format("%s %s", id, value); + } else { + return String.format("%s %s=%s", id, name, value); + } + } + + static Pair> ofSdpString(final String sdp) { final String[] pair = sdp.split(" "); if (pair.length == 2) { final String id = pair[0]; - ImmutableList.Builder builder = new ImmutableList.Builder<>(); + final ImmutableList.Builder builder = new ImmutableList.Builder<>(); for (final String parameter : pair[1].split(";")) { final String[] parts = parameter.split("=", 2); if (parts.length == 2) { diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 2de77dbfe..0d3da3030 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -70,7 +70,7 @@ Надсиланням звіту про відмову ви допомагаєте удосконаленню месенджера.\nУвага: Звіти надсилатимуться розробниками з вашого облікового запису XMPP. Надіслати зараз Ніколи не питати знову - Не можу з\'єднатися з обліковим записом + Неможливо з\'єднатися з обліковим записом Не можу увімкнути режим багатьох облікових записів Перейти для керування обліковими записами Долучити файл @@ -149,14 +149,14 @@ Тимчасово вимкнено У мережі З\'єднання\u2026 - Не в мережі + Поза мережею Не авторизовано Сервер не знайдено Немає зв\'язку із мережею Не вдалося зареєструватися Ім\'я користувача вже використовується Реєстрацію виконано - Сервер не підтримує режстрацію + Сервер не підтримує реєстрацію Неправильний реєстраційний токен Узгодження TLS не відбулося Порушення політики @@ -182,7 +182,7 @@ Заблокувати адресу XMPP username@example.com Пароль - Це неправильна адреса XMPP + Це недійсна адреса XMPP Пам\'ять вичерпано. Завелике зображення. Бажаєте додати %s до своєї книги контактів? Інформація про сервер @@ -196,10 +196,10 @@ XEP-0163: Персональне (піктограми користувачів, OMEMO) XEP-0363: Обмін файлами через HTTP XEP-0357: Push-повідомлення - є - нема - Не вистачає повідомлення публічного ключа. - у мережі + так + ні + Бракує інформації про публічний ключ + зараз у мережі у мережі 1 хвилину тому у мережі %d хвилин тому у мережі 1 годину тому @@ -368,7 +368,7 @@ Вимкнути всі облікові записи Здійснити дію з Непов\'язаний - Не в мережі + Поза мережею Вигнанець Учасник Розширений режим @@ -669,7 +669,7 @@ Отримано повідомлення від незнайомця Заблокувати невідомий контакт Заблокувати весь домен - у мережі просто зараз + зараз у мережі Спробувати знову розшифрувати Помилка сесії Застарілий механізм SASL @@ -838,7 +838,7 @@ Електронна книга Оригінал (не стиснений) Відкрити - Фото профілю чату + Світлана профілю Виберіть обліковий запис Відновити з резервної копії Відновити @@ -902,7 +902,7 @@ Більшість користувачів вибирають \'jabber.network\' як одну з кращих пропозицій зі всіх публічних середовищ XMPP. Спосіб пошуку каналів Резервне копіювання - Про + Про застосунок Будь ласка, активуйте обліковий запис Здійснити виклик Вхідний виклик @@ -917,6 +917,7 @@ Викликаю Зайнято Неможливо здійснити виклик + З\'єднання втрачено Відхилені виклики Помилка застосунку Завершити @@ -930,6 +931,7 @@ Пропущені виклики Голосовий виклик Відеовиклик + Допомога Недоступний мікрофон Водночас можливо здійснювати лише один виклик. Назад до активного виклику diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 07324fe66..b94f7a156 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -1,18 +1,18 @@ 设置 - 新聊天 + 新对话 管理账户 管理账户 - 关闭聊天 + 关闭对话 联系人详情 群聊详情 频道详情 - 加密聊天 + 加密对话 添加账户 编辑名称 添加到通讯录 - 从畅聊通讯录中删除 + 从通讯录中删除 封禁联系人 解封联系人 封禁域名 @@ -21,24 +21,24 @@ 解封成员 管理账户 设置 - 通过畅聊分享 - 开始聊天 + 通过Conversations分享 + 开始对话 选择联系人 选择联系人 通过帐户分享 封禁列表 刚刚 - 一分钟前 + 1分钟前 %d分钟前 %d条未读消息 发送中… - 解密中。请稍候… + 正在解密信息。请稍候…… OpenPGP加密的信息 用户名已存在 无效用户名 管理员 所有者 - 版主 + 群主 成员 访客 将%s从XMPP联系人中移除?与该联系人的会话消息不会清除。 @@ -66,8 +66,8 @@ 解封 保存 完成 - 畅聊已崩溃 - 通过您的账户发送堆栈跟踪,可以帮助畅聊持续发展。 + Conversations已崩溃 + 使用您的XMPP帐户发送堆栈跟踪信息有助于Conversations的持续发展。 立即发送 不再询问 账户无法连接 @@ -97,16 +97,16 @@ 不加密发送 解密失败,可能是私钥不正确。 OpenKeychain - 畅聊使用了第三方程序OpenKeychain来加密、解密信息并管理您的密钥。\n\nOpenKeychain遵循GPLv3并且可以在F-Droid 和Google Play上获取。\n\n(之后请重启畅聊) + Conversations使用OpenKeychain加密和解密消息以及管理您的公钥。\n\nOpenKeychain遵循GPLv3许可并且可在F-Droid和Google Play上获取。\n\n(请稍后重启Conversations。) 重启 安装 请安装OpenKeychain以解密 提供… 等待… 无OpenPGP密钥 - 因您的联系人未公布公钥,畅聊未能成功加密您的信息。\n\n请通知对方设置OpenPGP。 + 因您的联系人未公布其公钥,无法加密您的信息。\n\n请通知您的联系人设置OpenPGP。 无OpenPGP密钥 - 因您的联系人未公布公钥,畅聊未能成功加密您的信息。\n\n请通知对方设置OpenPGP. + 因您的联系人未公布其公钥,无法加密您的信息。\n\n请通知您的联系人设置OpenPGP。 常规 接收文件 自动接收小于此大小的文件 @@ -124,7 +124,7 @@ 在其他设备上检测到活动之后,通知在此时间段内将被静音。 高级 从不发送崩溃报告 - 通过发送堆栈跟踪,您可以帮助畅聊持续发展 + 通过发送堆栈跟踪,您可以帮助Conversations持续发展 确认消息 让对方知道你收到并阅读了他们的消息 用户界面 @@ -408,15 +408,15 @@ 视频 图片 PDF文档 - Android程序 + Android App 联系人 头像已经发布! 正在发送%s 正在提供%s 隐藏离线联系人 - %s正在输入 + %s正在输入…… %s已停止输入 - %s正在输入 + %s正在输入…… %s已停止输入 输入通知 让对方知道你正在输入 @@ -499,8 +499,8 @@ 图片已分享给%s 图片已分享给%s 文本已分享给%s - 允许畅聊访问外部储存 - 允许畅聊使用摄像头 + 允许Conversations访问外部存储 + 允许Conversations使用摄像头 同步联系人 将服务器端联系人与本地联系人匹配可以显示联系人的全名与头像。\n\n此应用只在本地读取并匹配联系人。\n\n现在应用将请求联系人权限。
我们并不储存这些号码。\n\n更多信息请阅读隐私政策。接下来将请求通讯录权限。]]>
@@ -513,8 +513,8 @@ 总是 仅大图片 已启用节电模式 - 你的设备正在为畅聊进行电池优化,这可能导致通知的延迟甚至消息的丢失。\n建议禁用电池优化。 - 你的设备正在为畅聊进行电池优化,这可能导致通知的延迟甚至消息的丢失。\n你将会被提示禁用该功能。 + 你的设备正在对Conversations进行电池优化,这可能会导致通知延迟甚至消息丢失。\n建议禁用电池优化。 + 你的设备正在对Conversations进行电池优化,这可能会导致通知延迟甚至消息丢失。\n你将会被提示禁用该功能。 禁用 选择区域过大 (没有启用的账户) @@ -553,7 +553,7 @@ 广播使用应用的时间 - 让联系人知道你使用畅聊的时间 + 让你的联系人知道你使用Conversations的时间 隐私 主题 选择主题色彩 @@ -604,7 +604,7 @@ 盲目信任OMEMO密钥,可能会有人冒充对方发送消息 不信任的 无效二维码 - 清除相机缓存 + 清理缓存文件夹(由相机应用使用) 清除缓存 清除私密存储 清除保存私密文件的存储 (可以从服务器上重新下载) @@ -666,7 +666,7 @@ 消息 禁止私信 受保护的应用 - 为了在屏幕关闭时也可收到消息提醒,您需要将畅聊加入受保护的应用列表。 + 为了在屏幕关闭时也可收到消息提醒,您需要将Conversations加入受保护的应用列表。 接受未知的证书? 服务器证书未由已知证书机构签发。 接受不匹配的服务器名称? @@ -680,7 +680,7 @@ 编辑状态信息 编辑状态信息 禁用加密 - 畅聊无法向%1$s发送加密信息。这可能是由于您的联系人使用了无法处理OMEMO的过时服务器或客户端。 + Conversations无法向%1$s发送加密信息。这可能是由于您的联系人使用了无法处理OMEMO的过时服务器或客户端。 无法获取设备列表 无法获取密钥 提示:某些情况下,可以将对方加入联系人列表,以解决此问题。 @@ -713,7 +713,7 @@ 分享 无法开始录制 请等待… - 允许畅聊使用麦克风 + 允许Conversations使用麦克风 搜索消息 GIF动图 查看聊天 @@ -735,7 +735,7 @@ 群聊已被解散 无法保存录制的文件 前台服务 - 此通知类别用于显示表明畅聊正在运行的永久通知。 + 此通知类别用于显示表明Conversations正在运行的永久通知。 状态信息 连接问题 此通知类别用于显示帐户连接问题通知。 @@ -797,9 +797,9 @@ 暂时无法连接。请稍候再试。 无网络连接 请在%s后重试 - 频率过高 + 你被限制速率 尝试次数过多 - 您正在使用旧版应用。 + 您正在使用此应用程序的过时版本。 更新 此号码已在其他设备上登录。 请输入您的姓名。这样,对方就能知道您是谁。 @@ -819,7 +819,7 @@ 恢复备份 恢复 输入%s的密码以恢复备份 - 仅在迁移或丢失设备时恢复备份。 + 请勿使用恢复备份功能来尝试克隆安装的应用程序(同时运行)。恢复备份功能仅用于迁移或丢失原始设备的情况。 无法恢复备份。 无法解密备份。密码是否正确? 备份与恢复 @@ -893,6 +893,7 @@ 正在响铃 正忙 无法接通来电 + 连接丢失 通话已撤销 程序错误 挂断 @@ -906,6 +907,7 @@ 未接电话 语音通话 视频通话 + 帮助 麦克风不可用 只能同时打一通电话 返回正在进行的通话 diff --git a/src/quicksy/java/eu/siacs/conversations/utils/ProvisioningUtils.java b/src/quicksy/java/eu/siacs/conversations/utils/ProvisioningUtils.java new file mode 100644 index 000000000..5b6cfae07 --- /dev/null +++ b/src/quicksy/java/eu/siacs/conversations/utils/ProvisioningUtils.java @@ -0,0 +1,9 @@ +package eu.siacs.conversations.utils; + +import eu.siacs.conversations.ui.UriHandlerActivity; + +public class ProvisioningUtils { + public static void provision(UriHandlerActivity uriHandlerActivity, String result) { + throw new IllegalStateException("Quicksy does not support provisioning"); + } +}