From cf140a0109589010cc53ace5840ca066e9e8515a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 22 Jun 2025 14:24:36 +0200 Subject: [PATCH] move non-muc presence sending into PresenceManager --- .../eu/siacs/conversations/AppSettings.java | 36 +++- .../siacs/conversations/android/Device.java | 80 ++++++++ .../generator/PresenceGenerator.java | 5 - .../services/NotificationService.java | 14 +- .../services/UnifiedPushBroker.java | 13 +- .../services/XmppConnectionService.java | 192 ++++-------------- .../conversations/ui/EditAccountActivity.java | 3 +- .../siacs/conversations/ui/XmppActivity.java | 4 +- .../conversations/utils/PhoneHelper.java | 28 +-- .../eu/siacs/conversations/xmpp/Managers.java | 2 + .../conversations/xmpp/XmppConnection.java | 4 +- .../xmpp/jingle/JingleConnectionManager.java | 4 +- .../xmpp/manager/AvatarManager.java | 8 +- .../xmpp/manager/MultiUserChatManager.java | 14 ++ .../xmpp/manager/PresenceManager.java | 97 ++++++++- .../android/xmpp/processor/BindProcessor.java | 3 +- 16 files changed, 297 insertions(+), 210 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/android/Device.java create mode 100644 src/main/java/eu/siacs/conversations/xmpp/manager/MultiUserChatManager.java diff --git a/src/main/java/eu/siacs/conversations/AppSettings.java b/src/main/java/eu/siacs/conversations/AppSettings.java index 2f68ddea7cc4086b3a686fd592b4e924b44b492d..573c982ccf89ced73c218799cbfe0733a4de972b 100644 --- a/src/main/java/eu/siacs/conversations/AppSettings.java +++ b/src/main/java/eu/siacs/conversations/AppSettings.java @@ -27,13 +27,13 @@ public class AppSettings { public static final String BLIND_TRUST_BEFORE_VERIFICATION = "btbv"; public static final String AUTOMATIC_MESSAGE_DELETION = "automatic_message_deletion"; public static final String BROADCAST_LAST_ACTIVITY = "last_activity"; + public static final String SEND_CHAT_STATES = "chat_states"; public static final String THEME = "theme"; public static final String DYNAMIC_COLORS = "dynamic_colors"; public static final String SHOW_DYNAMIC_TAGS = "show_dynamic_tags"; public static final String OMEMO = "omemo"; public static final String ALLOW_SCREENSHOTS = "allow_screenshots"; public static final String RINGTONE = "call_ringtone"; - public static final String BTBV = "btbv"; public static final String CONFIRM_MESSAGES = "confirm_messages"; public static final String ALLOW_MESSAGE_CORRECTION = "allow_message_correction"; @@ -57,6 +57,7 @@ public class AppSettings { public static final String AUTO_ACCEPT_FILE_SIZE = "auto_accept_file_size"; private static final String ACCEPT_INVITES_FROM_STRANGERS = "accept_invites_from_strangers"; + private static final String NOTIFICATIONS_FROM_STRANGERS = "notifications_from_strangers"; private static final String INSTALLATION_ID = "im.conversations.android.install_id"; private static final String EXTERNAL_STORAGE_AUTHORITY = @@ -102,7 +103,7 @@ public class AppSettings { } public boolean isBTBVEnabled() { - return getBooleanPreference(BTBV, R.bool.btbv); + return getBooleanPreference(BLIND_TRUST_BEFORE_VERIFICATION, R.bool.btbv); } public boolean isTrustSystemCAStore() { @@ -145,11 +146,37 @@ public class AppSettings { return getBooleanPreference(BROADCAST_LAST_ACTIVITY, R.bool.last_activity); } + public boolean isUserManagedAvailability() { + return getBooleanPreference(MANUALLY_CHANGE_PRESENCE, R.bool.manually_change_presence); + } + + public boolean isAutomaticAvailability() { + return !isUserManagedAvailability(); + } + + public boolean isDndOnSilentMode() { + return getBooleanPreference(AppSettings.DND_ON_SILENT_MODE, R.bool.dnd_on_silent_mode); + } + + public boolean isTreatVibrateAsSilent() { + return getBooleanPreference( + AppSettings.TREAT_VIBRATE_AS_SILENT, R.bool.treat_vibrate_as_silent); + } + + public boolean isAwayWhenScreenLocked() { + return getBooleanPreference( + AppSettings.AWAY_WHEN_SCREEN_IS_OFF, R.bool.away_when_screen_off); + } + public boolean isUseTor() { return QuickConversationsService.isConversations() && getBooleanPreference(USE_TOR, R.bool.use_tor); } + public boolean isSendChatStates() { + return getBooleanPreference(SEND_CHAT_STATES, R.bool.chat_states); + } + public boolean isExtendedConnectionOptions() { return QuickConversationsService.isConversations() && getBooleanPreference( @@ -161,6 +188,11 @@ public class AppSettings { ACCEPT_INVITES_FROM_STRANGERS, R.bool.accept_invites_from_strangers); } + public boolean isNotificationsFromStrangers() { + return getBooleanPreference( + NOTIFICATIONS_FROM_STRANGERS, R.bool.notifications_from_strangers); + } + public boolean isKeepForegroundService() { return Compatibility.twentySix() || getBooleanPreference(KEEP_FOREGROUND_SERVICE, R.bool.enable_foreground_service); diff --git a/src/main/java/eu/siacs/conversations/android/Device.java b/src/main/java/eu/siacs/conversations/android/Device.java new file mode 100644 index 0000000000000000000000000000000000000000..92c9a1871e9245bc101e009a25b5045e8a53d319 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/android/Device.java @@ -0,0 +1,80 @@ +package eu.siacs.conversations.android; + +import android.app.KeyguardManager; +import android.app.NotificationManager; +import android.content.Context; +import android.media.AudioManager; +import android.os.Build; +import android.os.PowerManager; +import android.util.Log; +import eu.siacs.conversations.Config; + +public class Device { + + private final Context context; + + public Device(final Context context) { + this.context = context; + } + + public boolean isScreenLocked() { + final var keyguardManager = context.getSystemService(KeyguardManager.class); + final var powerManager = context.getSystemService(PowerManager.class); + final var locked = keyguardManager != null && keyguardManager.isKeyguardLocked(); + final boolean interactive; + try { + interactive = powerManager != null && powerManager.isInteractive(); + } catch (final Exception e) { + return false; + } + return locked || !interactive; + } + + public boolean isPhoneSilenced(final boolean vibrateIsSilent) { + final var notificationManager = context.getSystemService(NotificationManager.class); + final int filter = + notificationManager == null + ? NotificationManager.INTERRUPTION_FILTER_UNKNOWN + : notificationManager.getCurrentInterruptionFilter(); + final boolean notificationDnd = filter >= NotificationManager.INTERRUPTION_FILTER_PRIORITY; + final var audioManager = context.getSystemService(AudioManager.class); + final int ringerMode = + audioManager == null + ? AudioManager.RINGER_MODE_NORMAL + : audioManager.getRingerMode(); + try { + if (vibrateIsSilent) { + return notificationDnd || ringerMode != AudioManager.RINGER_MODE_NORMAL; + } else { + return notificationDnd || ringerMode == AudioManager.RINGER_MODE_SILENT; + } + } catch (final Throwable throwable) { + Log.d(Config.LOGTAG, "platform bug in isPhoneSilenced", throwable); + return notificationDnd; + } + } + + public boolean isPhysicalDevice() { + return !isEmulator(); + } + + private static boolean isEmulator() { + return (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) + || Build.FINGERPRINT.startsWith("generic") + || Build.FINGERPRINT.startsWith("unknown") + || Build.HARDWARE.contains("goldfish") + || Build.HARDWARE.contains("ranchu") + || Build.MODEL.contains("google_sdk") + || Build.MODEL.contains("Emulator") + || Build.MODEL.contains("Android SDK built for x86") + || Build.MANUFACTURER.contains("Genymotion") + || Build.PRODUCT.contains("sdk_google") + || Build.PRODUCT.contains("google_sdk") + || Build.PRODUCT.contains("sdk") + || Build.PRODUCT.contains("sdk_x86") + || Build.PRODUCT.contains("sdk_gphone64_arm64") + || Build.PRODUCT.contains("vbox86p") + || Build.PRODUCT.contains("emulator") + || Build.PRODUCT.contains("simulator"); + } +} diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java index 21301e6a7658f25a27c8ac0a2763ed59c1af02b4..a5368b38ccfb50e02d69530f6aed9086d1049c40 100644 --- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java @@ -12,11 +12,6 @@ public class PresenceGenerator extends AbstractGenerator { super(service); } - public im.conversations.android.xmpp.model.stanza.Presence selfPresence( - Account account, Presence.Availability status) { - return selfPresence(account, status, true); - } - public im.conversations.android.xmpp.model.stanza.Presence selfPresence( final Account account, final Presence.Availability status, final boolean personal) { final var connection = account.getXmppConnection(); diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 2db57aaa69f9d471438b2438cde3a10d520f2aa8..6b6e211338250cc3119fac773cab9fe729c07a7c 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -50,6 +50,7 @@ import com.google.common.primitives.Ints; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.android.Device; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -390,11 +391,12 @@ public class NotificationService { } private boolean notifyMessage(final Message message) { + final var appSettings = new AppSettings(mXmppConnectionService.getApplicationContext()); final Conversation conversation = (Conversation) message.getConversation(); return message.getStatus() == Message.STATUS_RECEIVED && !conversation.isMuted() && (conversation.alwaysNotify() || wasHighlightedOrPrivate(message)) - && (!conversation.isWithStranger() || notificationsFromStrangers()) + && (!conversation.isWithStranger() || appSettings.isNotificationsFromStrangers()) && message.getType() != Message.TYPE_RTP_SESSION; } @@ -403,11 +405,6 @@ public class NotificationService { && message.getStatus() == Message.STATUS_RECEIVED; } - public boolean notificationsFromStrangers() { - return mXmppConnectionService.getBooleanPreference( - "notifications_from_strangers", R.bool.notifications_from_strangers); - } - public void pushFromBacklog(final Message message) { if (notifyMessage(message)) { synchronized (notifications) { @@ -513,9 +510,8 @@ public class NotificationService { public void pushFailedDelivery(final Message message) { final Conversation conversation = (Conversation) message.getConversation(); - final boolean isScreenLocked = !mXmppConnectionService.isScreenLocked(); if (this.mIsInForeground - && isScreenLocked + && !new Device(mXmppConnectionService).isScreenLocked() && this.mOpenConversation == message.getConversation()) { Log.d( Config.LOGTAG, @@ -755,7 +751,7 @@ public class NotificationService { + ": suppressing notification because turned off"); return; } - final boolean isScreenLocked = mXmppConnectionService.isScreenLocked(); + final boolean isScreenLocked = new Device(mXmppConnectionService).isScreenLocked(); if (this.mIsInForeground && !isScreenLocked && this.mOpenConversation == message.getConversation()) { diff --git a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java index 5f5de553ddbad9f24306f35fb933a5df59223100..41d3ca7cbc1bf5c9134c3f7186ea3b2c2bde275f 100644 --- a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java +++ b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java @@ -29,8 +29,8 @@ import eu.siacs.conversations.receiver.UnifiedPushDistributor; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; +import eu.siacs.conversations.xmpp.manager.PresenceManager; import im.conversations.android.xmpp.model.stanza.Iq; -import im.conversations.android.xmpp.model.stanza.Presence; import im.conversations.android.xmpp.model.up.Push; import java.nio.charset.StandardCharsets; import java.text.ParseException; @@ -69,7 +69,10 @@ public class UnifiedPushBroker { if (transportAccount != null && transportAccount.getUuid().equals(account.getUuid())) { final UnifiedPushDatabase database = UnifiedPushDatabase.getInstance(service); if (database.hasEndpoints(transport)) { - sendDirectedPresence(transportAccount, transport.transport); + transportAccount + .getXmppConnection() + .getManager(PresenceManager.class) + .available(transport.transport); } Log.d( Config.LOGTAG, @@ -79,12 +82,6 @@ public class UnifiedPushBroker { } } - private void sendDirectedPresence(final Account account, Jid to) { - final var presence = new Presence(); - presence.setTo(to); - service.sendPresencePacket(account, presence); - } - public void renewUnifiedPushEndpoints() { renewUnifiedPushEndpoints(null); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index eca7e505814a47649cf260985e53304302b23110..b77ecc09f61b21c7807230c75c721de8fd3fa626 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -5,7 +5,6 @@ import static eu.siacs.conversations.utils.Compatibility.s; import android.Manifest; import android.annotation.SuppressLint; import android.app.AlarmManager; -import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -42,7 +41,6 @@ import android.text.TextUtils; import android.util.Log; import android.util.LruCache; import android.util.Pair; -import androidx.annotation.BoolRes; import androidx.annotation.IntegerRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -706,7 +704,7 @@ public class XmppConnectionService extends Service { }); case AudioManager.RINGER_MODE_CHANGED_ACTION: case NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED: - if (dndOnSilentMode()) { + if (appSettings.isDndOnSilentMode() && appSettings.isAutomaticAvailability()) { refreshAllPresences(); } break; @@ -714,7 +712,7 @@ public class XmppConnectionService extends Service { deactivateGracePeriod(); case Intent.ACTION_USER_PRESENT: case Intent.ACTION_SCREEN_OFF: - if (awayWhenScreenLocked()) { + if (appSettings.isAwayWhenScreenLocked() && appSettings.isAutomaticAvailability()) { refreshAllPresences(); } break; @@ -1052,25 +1050,6 @@ public class XmppConnectionService extends Service { } } - private boolean dndOnSilentMode() { - return getBooleanPreference(AppSettings.DND_ON_SILENT_MODE, R.bool.dnd_on_silent_mode); - } - - private boolean manuallyChangePresence() { - return getBooleanPreference( - AppSettings.MANUALLY_CHANGE_PRESENCE, R.bool.manually_change_presence); - } - - private boolean treatVibrateAsSilent() { - return getBooleanPreference( - AppSettings.TREAT_VIBRATE_AS_SILENT, R.bool.treat_vibrate_as_silent); - } - - private boolean awayWhenScreenLocked() { - return getBooleanPreference( - AppSettings.AWAY_WHEN_SCREEN_IS_OFF, R.bool.away_when_screen_off); - } - private String getCompressPicturesPreference() { return getPreferences() .getString( @@ -1078,55 +1057,6 @@ public class XmppConnectionService extends Service { getResources().getString(R.string.picture_compression)); } - private im.conversations.android.xmpp.model.stanza.Presence.Availability getTargetPresence() { - if (dndOnSilentMode() && isPhoneSilenced()) { - return im.conversations.android.xmpp.model.stanza.Presence.Availability.DND; - } else if (awayWhenScreenLocked() && isScreenLocked()) { - return im.conversations.android.xmpp.model.stanza.Presence.Availability.AWAY; - } else { - return im.conversations.android.xmpp.model.stanza.Presence.Availability.ONLINE; - } - } - - public boolean isScreenLocked() { - final KeyguardManager keyguardManager = getSystemService(KeyguardManager.class); - final PowerManager powerManager = getSystemService(PowerManager.class); - final boolean locked = keyguardManager != null && keyguardManager.isKeyguardLocked(); - final boolean interactive; - try { - interactive = powerManager != null && powerManager.isInteractive(); - } catch (final Exception e) { - return false; - } - return locked || !interactive; - } - - private boolean isPhoneSilenced() { - final NotificationManager notificationManager = getSystemService(NotificationManager.class); - final int filter = - notificationManager == null - ? NotificationManager.INTERRUPTION_FILTER_UNKNOWN - : notificationManager.getCurrentInterruptionFilter(); - final boolean notificationDnd = filter >= NotificationManager.INTERRUPTION_FILTER_PRIORITY; - final AudioManager audioManager = getSystemService(AudioManager.class); - final int ringerMode = - audioManager == null - ? AudioManager.RINGER_MODE_NORMAL - : audioManager.getRingerMode(); - try { - if (treatVibrateAsSilent()) { - return notificationDnd || ringerMode != AudioManager.RINGER_MODE_NORMAL; - } else { - return notificationDnd || ringerMode == AudioManager.RINGER_MODE_SILENT; - } - } catch (final Throwable throwable) { - Log.d( - Config.LOGTAG, - "platform bug in isPhoneSilenced (" + throwable.getMessage() + ")"); - return notificationDnd; - } - } - private void resetAllAttemptCounts(boolean reallyAll, boolean retryImmediately) { Log.d(Config.LOGTAG, "resetting all attempt counts"); for (Account account : accounts) { @@ -1433,7 +1363,7 @@ public class XmppConnectionService extends Service { } public void toggleScreenEventReceiver() { - if (awayWhenScreenLocked() && !manuallyChangePresence()) { + if (appSettings.isAwayWhenScreenLocked() && appSettings.isAutomaticAvailability()) { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); @@ -1655,8 +1585,8 @@ public class XmppConnectionService extends Service { return connection; } - public void sendChatState(Conversation conversation) { - if (sendChatStates()) { + public void sendChatState(final Conversation conversation) { + if (appSettings.isSendChatStates()) { final var packet = mMessageGenerator.generateChatState(conversation); sendMessagePacket(conversation.getAccount(), packet); } @@ -1848,7 +1778,7 @@ public class XmppConnectionService extends Service { mMessageGenerator.addDelay(packet, message.getTimeSent()); } if (conversation.setOutgoingChatState(Config.DEFAULT_CHAT_STATE)) { - if (this.sendChatStates()) { + if (this.appSettings.isSendChatStates()) { packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); } } @@ -3112,7 +3042,7 @@ public class XmppConnectionService extends Service { private void switchToForeground() { toggleSoftDisabled(false); - final boolean broadcastLastActivity = broadcastLastActivity(); + final boolean broadcastLastActivity = appSettings.isBroadcastLastActivity(); for (Conversation conversation : getConversations()) { if (conversation.getMode() == Conversation.MODE_MULTI) { conversation.getMucOptions().resetChatState(); @@ -3120,45 +3050,41 @@ public class XmppConnectionService extends Service { conversation.setIncomingChatState(Config.DEFAULT_CHAT_STATE); } } - for (Account account : getAccounts()) { - if (account.getStatus() == Account.State.ONLINE) { - account.deactivateGracePeriod(); - final XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - if (connection.getFeatures().csi()) { - connection.sendActive(); - } - if (broadcastLastActivity) { - sendPresence( - account, - false); // send new presence but don't include idle because we are - // not - } - } + for (final var account : getAccounts()) { + if (account.getStatus() != Account.State.ONLINE) { + continue; + } + account.deactivateGracePeriod(); + final XmppConnection connection = account.getXmppConnection(); + if (connection.getFeatures().csi()) { + connection.sendActive(); + } + if (broadcastLastActivity) { + // send new presence but don't include idle because we are not + connection.getManager(PresenceManager.class).available(false); } } Log.d(Config.LOGTAG, "app switched into foreground"); } private void switchToBackground() { - final boolean broadcastLastActivity = broadcastLastActivity(); + final boolean broadcastLastActivity = appSettings.isBroadcastLastActivity(); if (broadcastLastActivity) { mLastActivity = System.currentTimeMillis(); final SharedPreferences.Editor editor = getPreferences().edit(); editor.putLong(SETTING_LAST_ACTIVITY_TS, mLastActivity); editor.apply(); } - for (Account account : getAccounts()) { - if (account.getStatus() == Account.State.ONLINE) { - XmppConnection connection = account.getXmppConnection(); - if (connection != null) { - if (broadcastLastActivity) { - sendPresence(account, true); - } - if (connection.getFeatures().csi()) { - connection.sendInactive(); - } - } + for (final var account : getAccounts()) { + if (account.getStatus() != Account.State.ONLINE) { + continue; + } + final var connection = account.getXmppConnection(); + if (broadcastLastActivity) { + connection.getManager(PresenceManager.class).available(true); + } + if (connection.getFeatures().csi()) { + connection.sendInactive(); } } this.mNotificationService.setIsInForeground(false); @@ -4218,7 +4144,7 @@ public class XmppConnectionService extends Service { } } } - sendOfflinePresence(account); + connection.getManager(PresenceManager.class).unavailable(); } connection.disconnect(force); } @@ -4549,10 +4475,6 @@ public class XmppConnectionService extends Service { } } - public boolean getBooleanPreference(String name, @BoolRes int res) { - return getPreferences().getBoolean(name, getResources().getBoolean(res)); - } - public boolean confirmMessages() { return appSettings.isConfirmMessages(); } @@ -4561,18 +4483,10 @@ public class XmppConnectionService extends Service { return appSettings.isAllowMessageCorrection(); } - public boolean sendChatStates() { - return getBooleanPreference("chat_states", R.bool.chat_states); - } - public boolean useTorToConnect() { return appSettings.isUseTor(); } - public boolean broadcastLastActivity() { - return appSettings.isBroadcastLastActivity(); - } - public int unreadCount() { int count = 0; for (Conversation conversation : getConversations()) { @@ -5014,27 +4928,6 @@ public class XmppConnectionService extends Service { } } - public void sendPresence(final Account account) { - sendPresence(account, checkListeners() && broadcastLastActivity()); - } - - private void sendPresence(final Account account, final boolean includeIdleTimestamp) { - final im.conversations.android.xmpp.model.stanza.Presence.Availability status; - if (manuallyChangePresence()) { - status = account.getPresenceStatus(); - } else { - status = getTargetPresence(); - } - final var packet = mPresenceGenerator.selfPresence(account, status); - if (mLastActivity > 0 && includeIdleTimestamp) { - long since = - Math.min(mLastActivity, System.currentTimeMillis()); // don't send future dates - packet.addChild("idle", Namespace.IDLE) - .setAttribute("since", AbstractGenerator.getTimestamp(since)); - } - sendPresencePacket(account, packet); - } - private void deactivateGracePeriod() { for (Account account : getAccounts()) { account.deactivateGracePeriod(); @@ -5042,10 +4935,13 @@ public class XmppConnectionService extends Service { } public void refreshAllPresences() { - boolean includeIdleTimestamp = checkListeners() && broadcastLastActivity(); - for (Account account : getAccounts()) { + final boolean includeIdleTimestamp = + checkListeners() && appSettings.isBroadcastLastActivity(); + for (final var account : getAccounts()) { if (account.isConnectionEnabled()) { - sendPresence(account, includeIdleTimestamp); + account.getXmppConnection() + .getManager(PresenceManager.class) + .available(includeIdleTimestamp); } } } @@ -5058,11 +4954,6 @@ public class XmppConnectionService extends Service { } } - private void sendOfflinePresence(final Account account) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": sending offline presence"); - sendPresencePacket(account, mPresenceGenerator.sendOfflinePresence(account)); - } - public MessageGenerator getMessageGenerator() { return this.mMessageGenerator; } @@ -5247,7 +5138,8 @@ public class XmppConnectionService extends Service { return mPushManagementService; } - public void changeStatus(Account account, PresenceTemplate template, String signature) { + public void changeStatus( + final Account account, final PresenceTemplate template, final String signature) { if (!template.getStatusMessage().isEmpty()) { databaseBackend.insertPresenceTemplate(template); } @@ -5255,7 +5147,7 @@ public class XmppConnectionService extends Service { account.setPresenceStatus(template.getStatus()); account.setPresenceStatusMessage(template.getStatusMessage()); databaseBackend.updateAccount(account); - sendPresence(account); + account.getXmppConnection().getManager(PresenceManager.class).available(); } public List getPresenceTemplates(Account account) { @@ -5345,6 +5237,10 @@ public class XmppConnectionService extends Service { } } + public long getLastActivity() { + return this.mLastActivity; + } + public interface OnMamPreferencesFetched { void onPreferencesFetched(Element prefs); diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 1f2a58d6cd6d1ed08230e123d7e540cce92168ef..973e886fe7f49ede5d40d8f5ed1f27035764d6b0 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -82,6 +82,7 @@ import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.XmppConnection.Features; import eu.siacs.conversations.xmpp.manager.CarbonsManager; import eu.siacs.conversations.xmpp.manager.HttpUploadManager; +import eu.siacs.conversations.xmpp.manager.PresenceManager; import eu.siacs.conversations.xmpp.manager.RegistrationManager; import im.conversations.android.xmpp.model.data.Data; import im.conversations.android.xmpp.model.stanza.Presence; @@ -1518,7 +1519,7 @@ public class EditAccountActivity extends OmemoActivity mAccount.setPgpSignId(0); mAccount.unsetPgpSignature(); xmppConnectionService.databaseBackend.updateAccount(mAccount); - xmppConnectionService.sendPresence(mAccount); + mAccount.getXmppConnection().getManager(PresenceManager.class).available(); refreshUiReal(); }); builder.create().show(); diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 0aaabdfdd43105b204aef53e7610c23d765f7bb9..285e08420ad802cb3e163cb3a657dc88090555a0 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -828,7 +828,9 @@ public abstract class XmppActivity extends ActionBarActivity { public void success(String signature) { account.setPgpSignature(signature); xmppConnectionService.databaseBackend.updateAccount(account); - xmppConnectionService.sendPresence(account); + account.getXmppConnection() + .getManager(PresenceManager.class) + .available(); if (conversation != null) { conversation.setNextEncryption(Message.ENCRYPTION_PGP); xmppConnectionService.updateConversation(conversation); diff --git a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java index 98924a26214c2ec03a33f43c2987e08f155237ae..e1189d813cfb957be009f201fb501baad90c8628 100644 --- a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java @@ -6,12 +6,9 @@ import android.content.Context; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; -import android.os.Build; import android.provider.ContactsContract.Profile; import android.provider.Settings; - import com.google.common.base.Strings; - import eu.siacs.conversations.services.QuickConversationsService; public class PhoneHelper { @@ -23,9 +20,8 @@ public class PhoneHelper { public static Uri getProfilePictureUri(final Context context) { if (!QuickConversationsService.isContactListIntegration(context) - || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) - != PackageManager.PERMISSION_GRANTED)) { + || context.checkSelfPermission(Manifest.permission.READ_CONTACTS) + != PackageManager.PERMISSION_GRANTED) { return null; } final String[] projection = new String[] {Profile._ID, Profile.PHOTO_URI}; @@ -42,24 +38,4 @@ public class PhoneHelper { } return null; } - - public static boolean isEmulator() { - return (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) - || Build.FINGERPRINT.startsWith("generic") - || Build.FINGERPRINT.startsWith("unknown") - || Build.HARDWARE.contains("goldfish") - || Build.HARDWARE.contains("ranchu") - || Build.MODEL.contains("google_sdk") - || Build.MODEL.contains("Emulator") - || Build.MODEL.contains("Android SDK built for x86") - || Build.MANUFACTURER.contains("Genymotion") - || Build.PRODUCT.contains("sdk_google") - || Build.PRODUCT.contains("google_sdk") - || Build.PRODUCT.contains("sdk") - || Build.PRODUCT.contains("sdk_x86") - || Build.PRODUCT.contains("sdk_gphone64_arm64") - || Build.PRODUCT.contains("vbox86p") - || Build.PRODUCT.contains("emulator") - || Build.PRODUCT.contains("simulator"); - } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/Managers.java b/src/main/java/eu/siacs/conversations/xmpp/Managers.java index 7d9e7bfe8d7bf99971e15261aa09afa6f0961f31..b14da3b7d3176eb60c0b45957877cdd6465d4305 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/Managers.java +++ b/src/main/java/eu/siacs/conversations/xmpp/Managers.java @@ -14,6 +14,7 @@ import eu.siacs.conversations.xmpp.manager.EntityTimeManager; import eu.siacs.conversations.xmpp.manager.HttpUploadManager; import eu.siacs.conversations.xmpp.manager.LegacyBookmarkManager; import eu.siacs.conversations.xmpp.manager.MessageDisplayedSynchronizationManager; +import eu.siacs.conversations.xmpp.manager.MultiUserChatManager; import eu.siacs.conversations.xmpp.manager.NickManager; import eu.siacs.conversations.xmpp.manager.OfflineMessagesManager; import eu.siacs.conversations.xmpp.manager.PepManager; @@ -48,6 +49,7 @@ public class Managers { .put( MessageDisplayedSynchronizationManager.class, new MessageDisplayedSynchronizationManager(context, connection)) + .put(MultiUserChatManager.class, new MultiUserChatManager(context, connection)) .put(NickManager.class, new NickManager(context, connection)) .put(OfflineMessagesManager.class, new OfflineMessagesManager(context, connection)) .put(PepManager.class, new PepManager(context, connection)) diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index a3b51cabbd3b9a215f41b6c534d243fc36a5d47d..f19361cab73b87f888370d5d9f7d1472689d2cd1 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -31,6 +31,7 @@ import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.BuildConfig; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.android.Device; import eu.siacs.conversations.crypto.PgpDecryptionService; import eu.siacs.conversations.crypto.XmppDomainVerifier; import eu.siacs.conversations.crypto.axolotl.AxolotlService; @@ -54,7 +55,6 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.utils.AccountUtils; import eu.siacs.conversations.utils.CryptoHelper; -import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.utils.Resolver; import eu.siacs.conversations.utils.SSLSockets; import eu.siacs.conversations.utils.SocksSocketFactory; @@ -1799,7 +1799,7 @@ public class XmppConnection implements Runnable { account, appSettings.getInstallationId()))); userAgent.setSoftware( String.format("%s %s", BuildConfig.APP_NAME, BuildConfig.VERSION_NAME)); - if (!PhoneHelper.isEmulator()) { + if (new Device(mXmppConnectionService).isPhysicalDevice()) { userAgent.setDevice(String.format("%s %s", Build.MANUFACTURER, Build.MODEL)); } // do not include bind if 'inlineStreamManagement' is missing and we have a streamId diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 9d2ff4434a513d6a9013c61733a4ee1fdf230dd7..792aabd8ed3e5137aa2cf9a91be0f6ca175f216d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -13,6 +13,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.collect.Collections2; import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableSet; +import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; @@ -249,7 +250,8 @@ public class JingleConnectionManager extends AbstractConnectionManager { private boolean isWithStrangerAndStrangerNotificationsAreOff(final Account account, Jid with) { final boolean notifyForStrangers = - mXmppConnectionService.getNotificationService().notificationsFromStrangers(); + new AppSettings(mXmppConnectionService.getApplicationContext()) + .isNotificationsFromStrangers(); if (notifyForStrangers) { return false; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/AvatarManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/AvatarManager.java index 6eebf7d76e1ad26bae8450f4ef0e6a50ea362108..73db1a9abff60c83fd4c0d4dbd9abd1ddb78cd44 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/manager/AvatarManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/manager/AvatarManager.java @@ -28,13 +28,13 @@ import com.google.common.util.concurrent.SettableFuture; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.android.Device; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.Compatibility; -import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; @@ -622,7 +622,9 @@ public class AvatarManager extends AbstractManager { resizeAndStoreAvatarAsync( image, Config.AVATAR_FULL_SIZE, ImageFormat.JPEG, autoAcceptFileSize); - if (Compatibility.twentyEight() && !PhoneHelper.isEmulator()) { + final var device = new Device(context); + + if (Compatibility.twentyEight() && device.isPhysicalDevice()) { final var avatarHeifFuture = resizeAndStoreAvatarAsync( image, @@ -634,7 +636,7 @@ public class AvatarManager extends AbstractManager { avatarHeifFuture, this::upload, MoreExecutors.directExecutor()); avatarFutures.add(avatarHeifWithUrlFuture); } - if (Compatibility.thirtyFour() && !PhoneHelper.isEmulator()) { + if (Compatibility.thirtyFour() && device.isPhysicalDevice()) { final var avatarAvifFuture = resizeAndStoreAvatarAsync( image, diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/MultiUserChatManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/MultiUserChatManager.java new file mode 100644 index 0000000000000000000000000000000000000000..8bd5b39be4c9accfa1894602f5f8bda1396bb204 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/manager/MultiUserChatManager.java @@ -0,0 +1,14 @@ +package eu.siacs.conversations.xmpp.manager; + +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.xmpp.XmppConnection; + +public class MultiUserChatManager extends AbstractManager { + + private final XmppConnectionService service; + + public MultiUserChatManager(final XmppConnectionService service, XmppConnection connection) { + super(service.getApplicationContext(), connection); + this.service = service; + } +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/PresenceManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/PresenceManager.java index 21dc794be3e171a7f2b8573c235c3850b95b18a8..9059e1fa220e089acb79e90cf64af8a00ccd06cf 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/manager/PresenceManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/manager/PresenceManager.java @@ -1,12 +1,19 @@ package eu.siacs.conversations.xmpp.manager; -import android.content.Context; +import android.util.Log; import com.google.common.base.Strings; +import eu.siacs.conversations.AppSettings; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.android.Device; +import eu.siacs.conversations.generator.AbstractGenerator; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; import im.conversations.android.xmpp.EntityCapabilities; import im.conversations.android.xmpp.EntityCapabilities2; import im.conversations.android.xmpp.ServiceDescription; +import im.conversations.android.xmpp.model.Extension; import im.conversations.android.xmpp.model.capabilties.Capabilities; import im.conversations.android.xmpp.model.capabilties.LegacyCapabilities; import im.conversations.android.xmpp.model.nick.Nick; @@ -18,11 +25,16 @@ import java.util.Map; public class PresenceManager extends AbstractManager { + private final XmppConnectionService service; + private final AppSettings appSettings; + private final Map serviceDescriptions = new HashMap<>(); - public PresenceManager(Context context, XmppConnection connection) { - super(context, connection); + public PresenceManager(final XmppConnectionService service, final XmppConnection connection) { + super(service.getApplicationContext(), connection); + this.appSettings = new AppSettings(service.getApplicationContext()); + this.service = service; } public void subscribe(final Jid address) { @@ -62,6 +74,85 @@ public class PresenceManager extends AbstractManager { this.connection.sendPresencePacket(presence); } + public void available() { + available(service.checkListeners() && appSettings.isBroadcastLastActivity()); + } + + public void available(final boolean withIdle) { + final var account = connection.getAccount(); + final var serviceDiscoveryFeatures = getManager(DiscoManager.class).getServiceDescription(); + final var infoQuery = serviceDiscoveryFeatures.asInfoQuery(); + final var capsHash = EntityCapabilities.hash(infoQuery); + final var caps2Hash = EntityCapabilities2.hash(infoQuery); + serviceDescriptions.put(capsHash, serviceDiscoveryFeatures); + serviceDescriptions.put(caps2Hash, serviceDiscoveryFeatures); + final var capabilities = new Capabilities(); + capabilities.setHash(caps2Hash); + final var legacyCapabilities = new LegacyCapabilities(); + legacyCapabilities.setNode(DiscoManager.CAPABILITY_NODE); + legacyCapabilities.setHash(capsHash); + final var presence = new Presence(); + presence.addExtension(capabilities); + presence.addExtension(legacyCapabilities); + final String pgpSignature = account.getPgpSignature(); + final String message = account.getPresenceStatusMessage(); + final Presence.Availability availability; + if (appSettings.isUserManagedAvailability()) { + availability = account.getPresenceStatus(); + } else { + availability = getTargetPresence(); + } + presence.setAvailability(availability); + presence.setStatus(message); + if (pgpSignature != null) { + final var signed = new Signed(); + signed.setContent(pgpSignature); + presence.addExtension(signed); + } + + final var lastActivity = service.getLastActivity(); + if (lastActivity > 0 && withIdle) { + final long since = + Math.min(lastActivity, System.currentTimeMillis()); // don't send future dates + presence.addChild("idle", Namespace.IDLE) + .setAttribute("since", AbstractGenerator.getTimestamp(since)); + } + Log.d(Config.LOGTAG, "--> " + presence); + connection.sendPresencePacket(presence); + } + + public void unavailable() { + var presence = new Presence(Presence.Type.UNAVAILABLE); + this.connection.sendPresencePacket(presence); + } + + public void available(final Jid to, final Extension... extensions) { + final var presence = new Presence(); + presence.setTo(to); + for (final var extension : extensions) { + presence.addExtension(extension); + } + connection.sendPresencePacket(presence); + } + + public void unavailable(final Jid to) { + final var presence = new Presence(Presence.Type.UNAVAILABLE); + presence.setTo(to); + connection.sendPresencePacket(presence); + } + + private im.conversations.android.xmpp.model.stanza.Presence.Availability getTargetPresence() { + final var device = new Device(context); + if (appSettings.isDndOnSilentMode() + && device.isPhoneSilenced(appSettings.isTreatVibrateAsSilent())) { + return im.conversations.android.xmpp.model.stanza.Presence.Availability.DND; + } else if (appSettings.isAwayWhenScreenLocked() && device.isScreenLocked()) { + return im.conversations.android.xmpp.model.stanza.Presence.Availability.AWAY; + } else { + return im.conversations.android.xmpp.model.stanza.Presence.Availability.ONLINE; + } + } + public Presence getPresence(final Presence.Availability availability, final boolean personal) { final var account = connection.getAccount(); final var serviceDiscoveryFeatures = getManager(DiscoManager.class).getServiceDescription(); diff --git a/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java index 6207deea86caca592b5dcddf885b99f34a31de5c..7226dc7c1d87c81ab32ea8fdf438ceef4b21333c 100644 --- a/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java +++ b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java @@ -16,6 +16,7 @@ import eu.siacs.conversations.xmpp.manager.LegacyBookmarkManager; import eu.siacs.conversations.xmpp.manager.MessageDisplayedSynchronizationManager; import eu.siacs.conversations.xmpp.manager.NickManager; import eu.siacs.conversations.xmpp.manager.OfflineMessagesManager; +import eu.siacs.conversations.xmpp.manager.PresenceManager; import eu.siacs.conversations.xmpp.manager.PrivateStorageManager; import eu.siacs.conversations.xmpp.manager.RosterManager; @@ -115,7 +116,7 @@ public class BindProcessor extends XmppConnection.Delegate implements Runnable { } else { trackOfflineMessageRetrieval = true; } - service.sendPresence(account); + getManager(PresenceManager.class).available(); connection.trackOfflineMessageRetrieval(trackOfflineMessageRetrieval); if (service.getPushManagementService().available(account)) { service.getPushManagementService().registerPushTokenOnServer(account);