diff options
author | Daniel Gultsch <daniel@gultsch.de> | 2020-12-01 22:31:30 +0300 |
---|---|---|
committer | Daniel Gultsch <daniel@gultsch.de> | 2020-12-01 22:31:30 +0300 |
commit | 1f392a688d1439fdf163cc767e3630d566726d83 (patch) | |
tree | e78f1903e43e3590ef6cd20db989cac21a3068cd /src/main | |
parent | 92083fec83d6dda988ae98f012308db62fe7bcdc (diff) |
initial (untested) support for easy onboarding invites
Diffstat (limited to 'src/main')
9 files changed, 225 insertions, 1 deletions
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 3225be67d..9adef0309 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -120,6 +120,7 @@ import eu.siacs.conversations.ui.interfaces.OnSearchResultsAvailable; import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.ConversationsFileObserver; import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.EasyOnboardingInvite; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.PhoneHelper; @@ -1619,6 +1620,43 @@ public class XmppConnectionService extends Service { sendMessage(message, true, delay); } + public void requestEasyOnboardingInvite(final Account account, final EasyOnboardingInvite.OnInviteRequested callback) { + final XmppConnection connection = account.getXmppConnection(); + final Jid jid = connection == null ? null : connection.getJidForCommand(Namespace.EASY_ONBOARDING_INVITE); + if (jid == null) { + callback.inviteRequestFailed(getString(R.string.server_does_not_support_easy_onboarding_invites)); + return; + } + final IqPacket request = new IqPacket(IqPacket.TYPE.SET); + request.setTo(jid); + final Element command = request.addChild("command", Namespace.COMMANDS); + command.setAttribute("node", Namespace.COMMANDS); + command.setAttribute("action", "execute"); + sendIqPacket(account, request, (a, response) -> { + if (response.getType() == IqPacket.TYPE.RESULT) { + final Element resultCommand = response.findChild("command", Namespace.COMMANDS); + final Element x = resultCommand == null ? null : resultCommand.findChild("x", Namespace.DATA); + if (x != null) { + final Data data = Data.parse(x); + final String uri = data.getValue("uri"); + final String landingUrl = data.getValue("landing-url"); + if (uri != null) { + final EasyOnboardingInvite invite = new EasyOnboardingInvite(jid.getDomain().toEscapedString(), uri, landingUrl); + callback.inviteRequested(invite); + return; + } + } + callback.inviteRequestFailed(getString(R.string.unable_to_parse_invite)); + Log.d(Config.LOGTAG, response.toString()); + } else if (response.getType() == IqPacket.TYPE.ERROR) { + callback.inviteRequestFailed(IqParser.extractErrorMessage(response)); + } else { + callback.inviteRequestFailed(getString(R.string.remote_server_timeout)); + } + }); + + } + public void fetchRosterFromServer(final Account account) { final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET); if (!"".equals(account.getRosterVersion())) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index f0c3bc181..07e35a50e 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -2117,6 +2117,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke this.binding.textinput.setKeyboardListener(this); messageListAdapter.updatePreferences(); refresh(false); + activity.invalidateOptionsMenu(); this.conversation.messagesLoaded.set(true); Log.d(Config.LOGTAG, "scrolledToBottomAndNoPending=" + Boolean.toString(scrolledToBottomAndNoPending)); @@ -2397,7 +2398,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } updateSendButton(); updateEditablity(); - activity.invalidateOptionsMenu(); } } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index 049c0f27e..026a0cafa 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -131,6 +131,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio @Override protected void refreshUiReal() { + invalidateOptionsMenu(); for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) { refreshFragment(id); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java index b76caaec7..ae81874e1 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java @@ -30,6 +30,7 @@ package eu.siacs.conversations.ui; import android.app.Activity; +import android.app.AlertDialog; import android.app.Fragment; import android.content.Intent; import android.databinding.DataBindingUtil; @@ -48,12 +49,16 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import com.google.common.collect.Collections2; + import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.FragmentConversationsOverviewBinding; +import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.ui.adapter.ConversationAdapter; @@ -65,6 +70,7 @@ import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.ui.util.ScrollState; import eu.siacs.conversations.ui.util.StyledAttributes; import eu.siacs.conversations.utils.AccountUtils; +import eu.siacs.conversations.utils.EasyOnboardingInvite; import eu.siacs.conversations.utils.ThemeHelper; import static android.support.v7.widget.helper.ItemTouchHelper.LEFT; @@ -300,6 +306,8 @@ public class ConversationsOverviewFragment extends XmppFragment { public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { menuInflater.inflate(R.menu.fragment_conversations_overview, menu); AccountUtils.showHideMenuItems(menu); + final MenuItem easyOnboardInvite = menu.findItem(R.id.action_easy_invite); + easyOnboardInvite.setVisible(EasyOnboardingInvite.anyHasSupport(activity == null ? null : activity.xmppConnectionService)); } @Override @@ -354,10 +362,33 @@ public class ConversationsOverviewFragment extends XmppFragment { case R.id.action_search: startActivity(new Intent(getActivity(), SearchActivity.class)); return true; + case R.id.action_easy_invite: + selectAccountToStartEasyInvite(); + return true; } return super.onOptionsItemSelected(item); } + private void selectAccountToStartEasyInvite() { + final List<Account> accounts = EasyOnboardingInvite.getSupportingAccounts(activity.xmppConnectionService); + if (accounts.size() == 1) { + openEasyInviteScreen(accounts.get(0)); + } else { + final AtomicReference<Account> selectedAccount = new AtomicReference<>(accounts.get(0)); + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity); + alertDialogBuilder.setTitle(R.string.choose_account); + final String[] asStrings = Collections2.transform(accounts, a -> a.getJid().asBareJid().toEscapedString()).toArray(new String[0]); + alertDialogBuilder.setSingleChoiceItems(asStrings, 0, (dialog, which) -> selectedAccount.set(accounts.get(which))); + alertDialogBuilder.setNegativeButton(R.string.cancel, null); + alertDialogBuilder.setPositiveButton(R.string.ok, (dialog, which) -> openEasyInviteScreen(selectedAccount.get())); + alertDialogBuilder.create().show(); + } + } + + private void openEasyInviteScreen(final Account account) { + EasyOnboardingInviteActivity.launch(account, activity); + } + @Override void refresh() { if (this.binding == null || this.activity == null) { diff --git a/src/main/java/eu/siacs/conversations/utils/EasyOnboardingInvite.java b/src/main/java/eu/siacs/conversations/utils/EasyOnboardingInvite.java new file mode 100644 index 000000000..954e2c65a --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/EasyOnboardingInvite.java @@ -0,0 +1,94 @@ +package eu.siacs.conversations.utils; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.common.collect.ImmutableList; + +import java.util.Collections; +import java.util.List; + +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.services.QuickConversationsService; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.xmpp.XmppConnection; + +public class EasyOnboardingInvite implements Parcelable { + + private String domain; + private String uri; + private String landingUrl; + + protected EasyOnboardingInvite(Parcel in) { + domain = in.readString(); + uri = in.readString(); + landingUrl = in.readString(); + } + + public EasyOnboardingInvite(String domain, String uri, String landingUrl) { + this.domain = domain; + this.uri = uri; + this.landingUrl = landingUrl; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(domain); + dest.writeString(uri); + dest.writeString(landingUrl); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<EasyOnboardingInvite> CREATOR = new Creator<EasyOnboardingInvite>() { + @Override + public EasyOnboardingInvite createFromParcel(Parcel in) { + return new EasyOnboardingInvite(in); + } + + @Override + public EasyOnboardingInvite[] newArray(int size) { + return new EasyOnboardingInvite[size]; + } + }; + + public static boolean anyHasSupport(final XmppConnectionService service) { + if (QuickConversationsService.isQuicksy()) { + return false; + } + return getSupportingAccounts(service).size() > 0; + + } + + public static List<Account> getSupportingAccounts(final XmppConnectionService service) { + final ImmutableList.Builder<Account> supportingAccountsBuilder = new ImmutableList.Builder<>(); + final List<Account> accounts = service == null ? Collections.emptyList() : service.getAccounts(); + for(Account account : accounts) { + final XmppConnection xmppConnection = account.getXmppConnection(); + if (xmppConnection != null && xmppConnection.getFeatures().easyOnboardingInvites()) { + supportingAccountsBuilder.add(account); + } + } + return supportingAccountsBuilder.build(); + } + + public String getUri() { + return uri; + } + + public String getLandingUrl() { + return landingUrl; + } + + public String getDomain() { + return domain; + } + + public interface OnInviteRequested { + void inviteRequested(EasyOnboardingInvite invite); + void inviteRequestFailed(String message); + } +} diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 31b3420dd..b65076016 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -52,4 +52,5 @@ public final class Namespace { public static final String BOOKMARKS2_COMPAT = BOOKMARKS2 + "#compat"; public static final String INVITE = "urn:xmpp:invite"; public static final String PARS = "urn:xmpp:pars:0"; + public static final String EASY_ONBOARDING_INVITE = "urn:xmpp:invite#invite"; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 9929a9c81..3f699e64e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -137,6 +137,7 @@ public class XmppConnection implements Runnable { protected final Account account; private final Features features = new Features(this); private final HashMap<Jid, ServiceDiscoveryResult> disco = new HashMap<>(); + private final HashMap<String, Jid> commands = new HashMap<>(); private final SparseArray<AbstractAcknowledgeableStanza> mStanzaQueue = new SparseArray<>(); private final Hashtable<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks = new Hashtable<>(); private final Set<OnAdvancedStreamFeaturesLoaded> advancedStreamFeaturesLoadedListeners = new HashSet<>(); @@ -228,6 +229,12 @@ public class XmppConnection implements Runnable { } } + public Jid getJidForCommand(final String node) { + synchronized (this.commands) { + return this.commands.get(node); + } + } + public void prepareNewConnection() { this.lastConnect = SystemClock.elapsedRealtime(); this.lastPingSent = SystemClock.elapsedRealtime(); @@ -1028,6 +1035,9 @@ public class XmppConnection implements Runnable { synchronized (this.disco) { disco.clear(); } + synchronized (this.commands) { + this.commands.clear(); + } } private void sendBindRequest() { @@ -1250,6 +1260,35 @@ public class XmppConnection implements Runnable { }); } + private void discoverCommands() { + final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + request.setTo(account.getDomain()); + request.addChild("query", Namespace.DISCO_ITEMS).setAttribute("node", Namespace.COMMANDS); + sendIqPacket(request, (account, response) -> { + if (response.getType() == IqPacket.TYPE.RESULT) { + final Element query = response.findChild("query",Namespace.DISCO_ITEMS); + if (query == null) { + return; + } + final HashMap<String, Jid> commands = new HashMap<>(); + for(final Element child : query.getChildren()) { + if ("item".equals(child.getName())) { + final String node = child.getAttribute("node"); + final Jid jid = child.getAttributeAsJid("jid"); + if (node != null && jid != null) { + commands.put(node, jid); + } + } + } + Log.d(Config.LOGTAG,commands.toString()); + synchronized (this.commands) { + this.commands.clear(); + this.commands.putAll(commands); + } + } + }); + } + public boolean isMamPreferenceAlways() { return isMamPreferenceAlways; } @@ -1273,6 +1312,9 @@ public class XmppConnection implements Runnable { if (getFeatures().carbons() && !features.carbonsEnabled) { sendEnableCarbons(); } + if (getFeatures().commands()) { + discoverCommands(); + } } private void sendServiceDiscoveryItems(final Jid server) { @@ -1788,6 +1830,16 @@ public class XmppConnection implements Runnable { return hasDiscoFeature(account.getDomain(), "urn:xmpp:carbons:2"); } + public boolean commands() { + return hasDiscoFeature(account.getDomain(), Namespace.COMMANDS); + } + + public boolean easyOnboardingInvites() { + synchronized (commands) { + return commands.containsKey(Namespace.EASY_ONBOARDING_INVITE); + } + } + public boolean bookmarksConversion() { return hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS_CONVERSION) && pepPublishOptions(); } diff --git a/src/main/res/menu/fragment_conversations_overview.xml b/src/main/res/menu/fragment_conversations_overview.xml index 52937ebd1..38c83df8f 100644 --- a/src/main/res/menu/fragment_conversations_overview.xml +++ b/src/main/res/menu/fragment_conversations_overview.xml @@ -36,6 +36,10 @@ android:visible="@bool/show_individual_search_options" app:showAsAction="never" /> <item + android:id="@+id/action_easy_invite" + android:orderInCategory="89" + android:title="@string/invite_to_app" /> + <item android:id="@+id/action_accounts" android:orderInCategory="90" android:title="@string/action_accounts" diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index f40317ea8..a9670533f 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -950,4 +950,7 @@ <string name="failed_deliveries">Failed deliveries</string> <string name="more_options">More options</string> <string name="no_application_found">No application found</string> + <string name="invite_to_app">Invite to Conversations</string> + <string name="unable_to_parse_invite">Unable to parse invite</string> + <string name="server_does_not_support_easy_onboarding_invites">Server does not support generating invites</string> </resources> |