From 9b69cb0f3b2f368fa972de6c797000295fdf1138 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sun, 20 Aug 2023 21:45:46 -0500 Subject: [PATCH] Retry onboarding completion If anything goes wrong, we just log. If app got killed in the middle of the process, we don't notice. So now instead we retry after 3 seconds whenever something goes wrong and we retry when the app comes back to the home screen if it didn't happen yet. Still careful to only ever do it once. --- .../cheogram/android/FinishOnboarding.java | 119 ++++++++++++++++++ .../services/XmppConnectionService.java | 19 +++ .../ui/ConversationsActivity.java | 6 + .../ui/StartConversationActivity.java | 80 +----------- 4 files changed, 147 insertions(+), 77 deletions(-) create mode 100644 src/cheogram/java/com/cheogram/android/FinishOnboarding.java diff --git a/src/cheogram/java/com/cheogram/android/FinishOnboarding.java b/src/cheogram/java/com/cheogram/android/FinishOnboarding.java new file mode 100644 index 000000000..ff0d944dc --- /dev/null +++ b/src/cheogram/java/com/cheogram/android/FinishOnboarding.java @@ -0,0 +1,119 @@ +package com.cheogram.android; + +import android.util.Log; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.XmppActivity; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xml.Namespace; +import eu.siacs.conversations.xmpp.Jid; +import eu.siacs.conversations.xmpp.forms.Data; +import eu.siacs.conversations.xmpp.stanzas.IqPacket; + +public class FinishOnboarding { + private static final AtomicBoolean WORKING = new AtomicBoolean(false); + private static ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1); + + private static void retry(final XmppConnectionService xmppConnectionService, final XmppActivity activity, final Account onboardAccount, final Account newAccount) { + WORKING.set(false); + SCHEDULER.schedule(() -> { + finish(xmppConnectionService, activity, onboardAccount, newAccount); + }, 3, TimeUnit.SECONDS); + } + + public static void finish(final XmppConnectionService xmppConnectionService, final XmppActivity activity, final Account onboardAccount, final Account newAccount) { + if (!WORKING.compareAndSet(false, true)) return; + + final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); + packet.setTo(Jid.of("cheogram.com")); + final Element c = packet.addChild("command", Namespace.COMMANDS); + c.setAttribute("node", "change jabber id"); + c.setAttribute("action", "execute"); + + Log.d(Config.LOGTAG, "" + packet); + xmppConnectionService.sendIqPacket(onboardAccount, packet, (a, iq) -> { + Element command = iq.findChild("command", "http://jabber.org/protocol/commands"); + if (command == null) { + Log.e(Config.LOGTAG, "Did not get expected data form from cheogram, got: " + iq); + retry(xmppConnectionService, activity, onboardAccount, newAccount); + return; + } + + Element form = command.findChild("x", "jabber:x:data"); + Data dataForm = form == null ? null : Data.parse(form); + if (dataForm == null || dataForm.getFieldByName("new-jid") == null) { + Log.e(Config.LOGTAG, "Did not get expected data form from cheogram, got: " + iq); + retry(xmppConnectionService, activity, onboardAccount, newAccount); + return; + } + + dataForm.put("new-jid", newAccount.getJid().toEscapedString()); + dataForm.submit(); + command.setAttribute("action", "execute"); + iq.setTo(iq.getFrom()); + iq.setAttribute("type", "set"); + iq.removeAttribute("from"); + iq.removeAttribute("id"); + xmppConnectionService.sendIqPacket(a, iq, (a2, iq2) -> { + Element command2 = iq2.findChild("command", "http://jabber.org/protocol/commands"); + if (command2 != null && command2.getAttribute("status") != null && command2.getAttribute("status").equals("completed")) { + final IqPacket regPacket = new IqPacket(IqPacket.TYPE.SET); + regPacket.setTo(Jid.of("cheogram.com/CHEOGRAM%jabber:iq:register")); + final Element c2 = regPacket.addChild("command", Namespace.COMMANDS); + c2.setAttribute("node", "jabber:iq:register"); + c2.setAttribute("action", "execute"); + xmppConnectionService.sendIqPacket(newAccount, regPacket, (a3, iq3) -> { + Element command3 = iq3.findChild("command", "http://jabber.org/protocol/commands"); + if (command3 == null) { + Log.e(Config.LOGTAG, "Did not get expected data form from cheogram, got: " + iq3); + retry(xmppConnectionService, activity, onboardAccount, newAccount); + return; + } + + Element form3 = command3.findChild("x", "jabber:x:data"); + Data dataForm3 = form3 == null ? null : Data.parse(form3); + if (dataForm3 == null || dataForm3.getFieldByName("confirm") == null) { + Log.e(Config.LOGTAG, "Did not get expected data form from cheogram, got: " + iq3); + retry(xmppConnectionService, activity, onboardAccount, newAccount); + return; + } + + dataForm3.put("confirm", "true"); + dataForm3.submit(); + command3.setAttribute("action", "execute"); + iq3.setTo(iq3.getFrom()); + iq3.setAttribute("type", "set"); + iq3.removeAttribute("from"); + iq3.removeAttribute("id"); + xmppConnectionService.sendIqPacket(newAccount, iq3, (a4, iq4) -> { + Element command4 = iq4.findChild("command", "http://jabber.org/protocol/commands"); + if (command4 != null && command4.getAttribute("status") != null && command4.getAttribute("status").equals("completed")) { + xmppConnectionService.createContact(newAccount.getRoster().getContact(iq4.getFrom().asBareJid()), true); + Conversation withCheogram = xmppConnectionService.findOrCreateConversation(newAccount, iq4.getFrom().asBareJid(), true, true, true); + xmppConnectionService.markRead(withCheogram); + xmppConnectionService.clearConversationHistory(withCheogram); + xmppConnectionService.deleteAccount(onboardAccount); + activity.switchToConversation(withCheogram, null, false, null, false, false, "command"); + // We don't set WORKING back to false because we suceeded so it should never run again anyway + } else { + Log.e(Config.LOGTAG, "Error confirming jid switch, got: " + iq4); + retry(xmppConnectionService, activity, onboardAccount, newAccount); + } + }); + }); + } else { + Log.e(Config.LOGTAG, "Error during jid switch, got: " + iq2); + retry(xmppConnectionService, activity, onboardAccount, newAccount); + } + }); + }); + } +} diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 7d1e3c38b..2d5a7f572 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -1994,6 +1994,25 @@ public class XmppConnectionService extends Service { sendMessage(message, true, false, delay); } + public Pair onboardingIncomplete() { + if (getAccounts().size() != 2) return null; + Account onboarding = null; + Account newAccount = null; + for (final Account account : getAccounts()) { + if (account.getJid().getDomain().equals(Config.ONBOARDING_DOMAIN)) { + onboarding = account; + } else { + newAccount = account; + } + } + + if (onboarding != null && newAccount != null) { + return new Pair<>(onboarding, newAccount); + } + + return null; + } + public boolean isOnboarding() { return getAccounts().size() == 1 && getAccounts().get(0).getJid().getDomain().equals(Config.ONBOARDING_DOMAIN); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index 990a601ab..ba328d1da 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -48,6 +48,7 @@ import android.os.Build; import android.os.Bundle; import android.provider.Settings; import android.util.Log; +import android.util.Pair; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; @@ -62,6 +63,7 @@ import androidx.core.content.ContextCompat; import androidx.databinding.DataBindingUtil; import com.cheogram.android.DownloadDefaultStickers; +import com.cheogram.android.FinishOnboarding; import com.google.common.collect.ImmutableList; @@ -223,6 +225,10 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void showDialogsIfMainIsOverview() { + Pair incomplete = null; + if (xmppConnectionService != null && (incomplete = xmppConnectionService.onboardingIncomplete()) != null) { + FinishOnboarding.finish(xmppConnectionService, this, incomplete.first, incomplete.second); + } if (xmppConnectionService == null || xmppConnectionService.isOnboarding()) { return; } diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 1b89d88bc..a73a994e8 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -57,6 +57,8 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; +import com.cheogram.android.FinishOnboarding; + import com.google.android.material.textfield.TextInputLayout; import com.leinardi.android.speeddial.SpeedDialActionItem; import com.leinardi.android.speeddial.SpeedDialView; @@ -955,83 +957,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne if (!hasPstnOrSms) { if (onboardingAccount != null && !selectedAccount.getJid().equals(onboardingAccount.getJid())) { - final Account onboardAccount = onboardingAccount; - final Account newAccount = selectedAccount; - final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); - packet.setTo(Jid.of("cheogram.com")); - final Element c = packet.addChild("command", Namespace.COMMANDS); - c.setAttribute("node", "change jabber id"); - c.setAttribute("action", "execute"); - - xmppConnectionService.sendIqPacket(onboardingAccount, packet, (a, iq) -> { - Element command = iq.findChild("command", "http://jabber.org/protocol/commands"); - if (command == null) { - Log.e(Config.LOGTAG, "Did not get expected data form from cheogram, got: " + iq); - return; - } - - Element form = command.findChild("x", "jabber:x:data"); - Data dataForm = form == null ? null : Data.parse(form); - if (dataForm == null || dataForm.getFieldByName("new-jid") == null) { - Log.e(Config.LOGTAG, "Did not get expected data form from cheogram, got: " + iq); - return; - } - - dataForm.put("new-jid", newAccount.getJid().toEscapedString()); - dataForm.submit(); - command.setAttribute("action", "execute"); - iq.setTo(iq.getFrom()); - iq.setAttribute("type", "set"); - iq.removeAttribute("from"); - iq.removeAttribute("id"); - xmppConnectionService.sendIqPacket(a, iq, (a2, iq2) -> { - Element command2 = iq2.findChild("command", "http://jabber.org/protocol/commands"); - if (command2 != null && command2.getAttribute("status") != null && command2.getAttribute("status").equals("completed")) { - final IqPacket regPacket = new IqPacket(IqPacket.TYPE.SET); - regPacket.setTo(Jid.of("cheogram.com/CHEOGRAM%jabber:iq:register")); - final Element c2 = regPacket.addChild("command", Namespace.COMMANDS); - c2.setAttribute("node", "jabber:iq:register"); - c2.setAttribute("action", "execute"); - xmppConnectionService.sendIqPacket(newAccount, regPacket, (a3, iq3) -> { - Element command3 = iq3.findChild("command", "http://jabber.org/protocol/commands"); - if (command3 == null) { - Log.e(Config.LOGTAG, "Did not get expected data form from cheogram, got: " + iq3); - return; - } - - Element form3 = command3.findChild("x", "jabber:x:data"); - Data dataForm3 = form3 == null ? null : Data.parse(form3); - if (dataForm3 == null || dataForm3.getFieldByName("confirm") == null) { - Log.e(Config.LOGTAG, "Did not get expected data form from cheogram, got: " + iq3); - return; - } - - dataForm3.put("confirm", "true"); - dataForm3.submit(); - command3.setAttribute("action", "execute"); - iq3.setTo(iq3.getFrom()); - iq3.setAttribute("type", "set"); - iq3.removeAttribute("from"); - iq3.removeAttribute("id"); - xmppConnectionService.sendIqPacket(newAccount, iq3, (a4, iq4) -> { - Element command4 = iq2.findChild("command", "http://jabber.org/protocol/commands"); - if (command4 != null && command4.getAttribute("status") != null && command4.getAttribute("status").equals("completed")) { - xmppConnectionService.createContact(newAccount.getRoster().getContact(iq4.getFrom().asBareJid()), true); - Conversation withCheogram = xmppConnectionService.findOrCreateConversation(newAccount, iq4.getFrom().asBareJid(), true, true, true); - xmppConnectionService.markRead(withCheogram); - xmppConnectionService.clearConversationHistory(withCheogram); - xmppConnectionService.deleteAccount(onboardAccount); - switchToConversation(withCheogram, null, false, null, false, false, "command"); - } else { - Log.e(Config.LOGTAG, "Error confirming jid switch, got: " + iq4); - } - }); - }); - } else { - Log.e(Config.LOGTAG, "Error during jid switch, got: " + iq2); - } - }); - }); + FinishOnboarding.finish(xmppConnectionService, this, onboardingAccount, selectedAccount); } else { startCommand(selectedAccount, Jid.of("cheogram.com/CHEOGRAM%jabber:iq:register"), "jabber:iq:register"); finish(); -- 2.45.2