2014-02-28 17:46:01 +00:00
package eu.siacs.conversations.services ;
2014-01-24 01:04:05 +00:00
2018-09-05 19:37:05 +00:00
import android.Manifest ;
2014-11-10 00:24:35 +00:00
import android.annotation.SuppressLint ;
2016-05-28 12:44:22 +00:00
import android.annotation.TargetApi ;
2014-11-10 00:24:35 +00:00
import android.app.AlarmManager ;
2019-02-16 10:58:16 +00:00
import android.app.Notification ;
2018-09-06 21:17:37 +00:00
import android.app.NotificationManager ;
2014-11-10 00:24:35 +00:00
import android.app.PendingIntent ;
import android.app.Service ;
2018-09-06 21:17:37 +00:00
import android.content.BroadcastReceiver ;
2019-01-13 14:28:24 +00:00
import android.content.ComponentName ;
2014-11-10 00:24:35 +00:00
import android.content.Context ;
import android.content.Intent ;
2015-10-07 22:35:04 +00:00
import android.content.IntentFilter ;
2014-11-10 00:24:35 +00:00
import android.content.SharedPreferences ;
2018-06-24 18:54:01 +00:00
import android.content.pm.PackageManager ;
2014-11-10 00:24:35 +00:00
import android.database.ContentObserver ;
import android.graphics.Bitmap ;
2015-10-07 22:35:04 +00:00
import android.media.AudioManager ;
2014-11-10 00:24:35 +00:00
import android.net.ConnectivityManager ;
import android.net.NetworkInfo ;
import android.net.Uri ;
import android.os.Binder ;
2015-10-07 22:35:04 +00:00
import android.os.Build ;
2014-11-10 00:24:35 +00:00
import android.os.Bundle ;
2016-07-23 14:12:45 +00:00
import android.os.Environment ;
2014-11-10 00:24:35 +00:00
import android.os.IBinder ;
import android.os.PowerManager ;
import android.os.PowerManager.WakeLock ;
import android.os.SystemClock ;
import android.preference.PreferenceManager ;
import android.provider.ContactsContract ;
2015-10-09 11:37:08 +00:00
import android.security.KeyChain ;
2017-07-01 11:41:24 +00:00
import android.support.annotation.BoolRes ;
import android.support.annotation.IntegerRes ;
2016-08-25 15:30:44 +00:00
import android.support.v4.app.RemoteInput ;
2018-06-24 18:54:01 +00:00
import android.support.v4.content.ContextCompat ;
2020-08-24 07:51:26 +00:00
import android.telephony.PhoneStateListener ;
import android.telephony.TelephonyManager ;
2018-06-24 09:23:10 +00:00
import android.text.TextUtils ;
2015-10-16 07:58:31 +00:00
import android.util.DisplayMetrics ;
2014-11-10 00:24:35 +00:00
import android.util.Log ;
import android.util.LruCache ;
2015-10-11 13:48:58 +00:00
import android.util.Pair ;
2014-11-10 00:24:35 +00:00
2020-04-15 20:40:37 +00:00
import com.google.common.base.Objects ;
2019-11-02 08:43:37 +00:00
import com.google.common.base.Strings ;
2018-09-21 14:33:07 +00:00
import org.conscrypt.Conscrypt ;
2015-10-29 13:03:41 +00:00
import org.openintents.openpgp.IOpenPgpService2 ;
2014-11-10 00:24:35 +00:00
import org.openintents.openpgp.util.OpenPgpApi ;
import org.openintents.openpgp.util.OpenPgpServiceConnection ;
2019-01-10 13:52:27 +00:00
import java.io.File ;
2017-02-07 18:32:12 +00:00
import java.net.URL ;
2014-06-20 15:30:19 +00:00
import java.security.SecureRandom ;
2018-09-21 14:33:07 +00:00
import java.security.Security ;
2015-10-11 13:48:58 +00:00
import java.security.cert.CertificateException ;
2015-10-09 11:37:08 +00:00
import java.security.cert.X509Certificate ;
2014-07-10 17:42:37 +00:00
import java.util.ArrayList ;
2015-04-21 20:17:58 +00:00
import java.util.Arrays ;
2014-12-21 20:43:58 +00:00
import java.util.Collection ;
2014-03-19 15:16:40 +00:00
import java.util.Collections ;
2016-05-02 12:31:30 +00:00
import java.util.HashSet ;
2014-02-01 14:07:20 +00:00
import java.util.Hashtable ;
2015-05-05 08:29:41 +00:00
import java.util.Iterator ;
2014-01-25 18:33:12 +00:00
import java.util.List ;
2017-01-22 17:58:49 +00:00
import java.util.ListIterator ;
2014-12-21 20:43:58 +00:00
import java.util.Map ;
2018-03-11 11:13:56 +00:00
import java.util.Set ;
2018-07-06 18:15:45 +00:00
import java.util.WeakHashMap ;
2014-07-12 10:41:37 +00:00
import java.util.concurrent.CopyOnWriteArrayList ;
2018-01-20 19:05:39 +00:00
import java.util.concurrent.CountDownLatch ;
2017-05-31 14:45:51 +00:00
import java.util.concurrent.atomic.AtomicBoolean ;
2017-01-23 16:14:30 +00:00
import java.util.concurrent.atomic.AtomicLong ;
2020-04-11 17:05:07 +00:00
import java.util.concurrent.atomic.AtomicReference ;
2014-02-13 22:40:08 +00:00
2014-08-31 14:28:21 +00:00
import eu.siacs.conversations.Config ;
2014-08-04 23:36:17 +00:00
import eu.siacs.conversations.R ;
2018-10-26 22:32:09 +00:00
import eu.siacs.conversations.android.JabberIdContact ;
2018-03-27 13:35:53 +00:00
import eu.siacs.conversations.crypto.OmemoSetting ;
2016-06-13 11:32:14 +00:00
import eu.siacs.conversations.crypto.PgpDecryptionService ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.crypto.PgpEngine ;
2015-10-16 21:48:42 +00:00
import eu.siacs.conversations.crypto.axolotl.AxolotlService ;
2016-11-17 19:09:42 +00:00
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus ;
2015-07-20 21:13:28 +00:00
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.entities.Account ;
2014-12-21 20:43:58 +00:00
import eu.siacs.conversations.entities.Blockable ;
2014-07-14 09:47:42 +00:00
import eu.siacs.conversations.entities.Bookmark ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.entities.Contact ;
import eu.siacs.conversations.entities.Conversation ;
2018-06-20 13:12:02 +00:00
import eu.siacs.conversations.entities.Conversational ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.entities.Message ;
2014-03-03 04:01:02 +00:00
import eu.siacs.conversations.entities.MucOptions ;
import eu.siacs.conversations.entities.MucOptions.OnRenameListener ;
2016-01-17 21:28:38 +00:00
import eu.siacs.conversations.entities.Presence ;
2016-04-22 19:25:06 +00:00
import eu.siacs.conversations.entities.PresenceTemplate ;
2016-02-03 09:40:02 +00:00
import eu.siacs.conversations.entities.Roster ;
import eu.siacs.conversations.entities.ServiceDiscoveryResult ;
2016-06-04 14:16:14 +00:00
import eu.siacs.conversations.generator.AbstractGenerator ;
2014-07-23 12:30:27 +00:00
import eu.siacs.conversations.generator.IqGenerator ;
2014-06-22 15:24:47 +00:00
import eu.siacs.conversations.generator.MessageGenerator ;
2014-07-12 01:44:23 +00:00
import eu.siacs.conversations.generator.PresenceGenerator ;
2018-05-25 10:24:23 +00:00
import eu.siacs.conversations.http.CustomURLStreamHandlerFactory ;
2019-08-19 12:27:11 +00:00
import eu.siacs.conversations.http.HttpConnectionManager ;
2016-05-17 12:25:58 +00:00
import eu.siacs.conversations.parser.AbstractParser ;
2014-07-12 10:28:28 +00:00
import eu.siacs.conversations.parser.IqParser ;
2014-05-14 10:56:34 +00:00
import eu.siacs.conversations.parser.MessageParser ;
2014-06-06 16:26:40 +00:00
import eu.siacs.conversations.parser.PresenceParser ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.persistance.DatabaseBackend ;
2014-04-05 19:06:10 +00:00
import eu.siacs.conversations.persistance.FileBackend ;
2019-01-13 14:28:24 +00:00
import eu.siacs.conversations.ui.ChooseAccountForProfilePictureActivity ;
2020-04-07 16:50:39 +00:00
import eu.siacs.conversations.ui.RtpSessionActivity ;
2016-11-21 10:03:38 +00:00
import eu.siacs.conversations.ui.SettingsActivity ;
2014-05-12 12:59:46 +00:00
import eu.siacs.conversations.ui.UiCallback ;
2018-06-18 12:15:19 +00:00
import eu.siacs.conversations.ui.interfaces.OnAvatarPublication ;
2018-09-15 17:38:45 +00:00
import eu.siacs.conversations.ui.interfaces.OnMediaLoaded ;
2018-04-26 11:22:31 +00:00
import eu.siacs.conversations.ui.interfaces.OnSearchResultsAvailable ;
2018-09-05 19:37:05 +00:00
import eu.siacs.conversations.utils.Compatibility ;
2016-07-23 14:12:45 +00:00
import eu.siacs.conversations.utils.ConversationsFileObserver ;
2014-06-20 15:30:19 +00:00
import eu.siacs.conversations.utils.CryptoHelper ;
2014-03-09 12:21:28 +00:00
import eu.siacs.conversations.utils.ExceptionHelper ;
2016-12-30 20:48:39 +00:00
import eu.siacs.conversations.utils.MimeUtils ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.utils.PhoneHelper ;
2018-04-11 16:07:40 +00:00
import eu.siacs.conversations.utils.QuickLoader ;
2016-05-31 15:20:21 +00:00
import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor ;
2018-03-18 15:46:50 +00:00
import eu.siacs.conversations.utils.ReplacingTaskManager ;
2017-06-21 21:28:01 +00:00
import eu.siacs.conversations.utils.Resolver ;
2015-06-05 06:46:06 +00:00
import eu.siacs.conversations.utils.SerialSingleThreadExecutor ;
2018-06-24 14:17:20 +00:00
import eu.siacs.conversations.utils.StringUtils ;
2020-08-19 11:57:33 +00:00
import eu.siacs.conversations.utils.TorServiceUtils ;
2018-04-15 16:31:58 +00:00
import eu.siacs.conversations.utils.WakeLockHelper ;
2016-11-17 19:09:42 +00:00
import eu.siacs.conversations.utils.XmppUri ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.xml.Element ;
2019-08-19 12:27:11 +00:00
import eu.siacs.conversations.xml.Namespace ;
2014-03-14 19:43:54 +00:00
import eu.siacs.conversations.xmpp.OnBindListener ;
2014-05-23 08:54:40 +00:00
import eu.siacs.conversations.xmpp.OnContactStatusChanged ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.xmpp.OnIqPacketReceived ;
2015-08-25 10:40:22 +00:00
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated ;
2014-08-26 14:52:42 +00:00
import eu.siacs.conversations.xmpp.OnMessageAcknowledged ;
2014-12-21 20:43:58 +00:00
import eu.siacs.conversations.xmpp.OnMessagePacketReceived ;
import eu.siacs.conversations.xmpp.OnPresencePacketReceived ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.xmpp.OnStatusChanged ;
2014-12-21 20:43:58 +00:00
import eu.siacs.conversations.xmpp.OnUpdateBlocklist ;
2017-02-19 12:05:40 +00:00
import eu.siacs.conversations.xmpp.Patches ;
2014-02-28 17:46:01 +00:00
import eu.siacs.conversations.xmpp.XmppConnection ;
2015-02-21 10:06:52 +00:00
import eu.siacs.conversations.xmpp.chatstate.ChatState ;
2014-11-20 17:33:04 +00:00
import eu.siacs.conversations.xmpp.forms.Data ;
2020-04-11 17:05:07 +00:00
import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection ;
2014-04-08 21:15:55 +00:00
import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager ;
2020-04-15 20:40:37 +00:00
import eu.siacs.conversations.xmpp.jingle.Media ;
2020-04-07 11:15:24 +00:00
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState ;
2017-05-07 19:05:35 +00:00
import eu.siacs.conversations.xmpp.mam.MamReference ;
2014-08-03 18:28:13 +00:00
import eu.siacs.conversations.xmpp.pep.Avatar ;
2018-08-18 14:27:50 +00:00
import eu.siacs.conversations.xmpp.pep.PublishOptions ;
2014-03-10 18:22:13 +00:00
import eu.siacs.conversations.xmpp.stanzas.IqPacket ;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket ;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket ;
2015-05-20 10:47:04 +00:00
import me.leolin.shortcutbadger.ShortcutBadger ;
2020-05-15 15:06:16 +00:00
import eu.siacs.conversations.xmpp.Jid ;
2014-01-24 01:04:05 +00:00
2016-05-31 15:20:21 +00:00
public class XmppConnectionService extends Service {
2014-02-03 17:38:47 +00:00
2018-08-18 14:27:50 +00:00
public static final String ACTION_REPLY_TO_CONVERSATION = " reply_to_conversations " ;
public static final String ACTION_MARK_AS_READ = " mark_as_read " ;
public static final String ACTION_SNOOZE = " snooze " ;
public static final String ACTION_CLEAR_NOTIFICATION = " clear_notification " ;
public static final String ACTION_DISMISS_ERROR_NOTIFICATIONS = " dismiss_error " ;
public static final String ACTION_TRY_AGAIN = " try_again " ;
public static final String ACTION_IDLE_PING = " idle_ping " ;
public static final String ACTION_FCM_TOKEN_REFRESH = " fcm_token_refresh " ;
public static final String ACTION_FCM_MESSAGE_RECEIVED = " fcm_message_received " ;
2020-04-07 16:50:39 +00:00
public static final String ACTION_DISMISS_CALL = " dismiss_call " ;
2020-04-10 13:19:56 +00:00
public static final String ACTION_END_CALL = " end_call " ;
2020-06-21 13:40:51 +00:00
public static final String ACTION_PROVISION_ACCOUNT = " provision_account " ;
2019-01-27 19:54:45 +00:00
private static final String ACTION_POST_CONNECTIVITY_CHANGE = " eu.siacs.conversations.POST_CONNECTIVITY_CHANGE " ;
2018-08-18 14:27:50 +00:00
private static final String SETTING_LAST_ACTIVITY_TS = " last_activity_timestamp " ;
static {
URL . setURLStreamHandlerFactory ( new CustomURLStreamHandlerFactory ( ) ) ;
}
public final CountDownLatch restoredFromDatabaseLatch = new CountDownLatch ( 1 ) ;
private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor ( " FileAdding " ) ;
private final SerialSingleThreadExecutor mVideoCompressionExecutor = new SerialSingleThreadExecutor ( " VideoCompression " ) ;
private final SerialSingleThreadExecutor mDatabaseWriterExecutor = new SerialSingleThreadExecutor ( " DatabaseWriter " ) ;
private final SerialSingleThreadExecutor mDatabaseReaderExecutor = new SerialSingleThreadExecutor ( " DatabaseReader " ) ;
private final SerialSingleThreadExecutor mNotificationExecutor = new SerialSingleThreadExecutor ( " NotificationExecutor " ) ;
private final ReplacingTaskManager mRosterSyncTaskManager = new ReplacingTaskManager ( ) ;
private final IBinder mBinder = new XmppConnectionBinder ( ) ;
private final List < Conversation > conversations = new CopyOnWriteArrayList < > ( ) ;
private final IqGenerator mIqGenerator = new IqGenerator ( this ) ;
2019-01-17 16:55:47 +00:00
private final Set < String > mInProgressAvatarFetches = new HashSet < > ( ) ;
private final Set < String > mOmittedPepAvatarFetches = new HashSet < > ( ) ;
2018-08-18 14:27:50 +00:00
private final HashSet < Jid > mLowPingTimeoutMode = new HashSet < > ( ) ;
private final OnIqPacketReceived mDefaultIqHandler = ( account , packet ) - > {
if ( packet . getType ( ) ! = IqPacket . TYPE . RESULT ) {
Element error = packet . findChild ( " error " ) ;
String text = error ! = null ? error . findChildContent ( " text " ) : null ;
if ( text ! = null ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : received iq error - " + text ) ;
}
}
} ;
public DatabaseBackend databaseBackend ;
2018-10-26 22:32:09 +00:00
private ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor ( " ContactMerger " ) ;
2018-08-18 14:27:50 +00:00
private long mLastActivity = 0 ;
private FileBackend fileBackend = new FileBackend ( this ) ;
private MemorizingTrustManager mMemorizingTrustManager ;
private NotificationService mNotificationService = new NotificationService ( this ) ;
2019-04-24 11:25:54 +00:00
private ChannelDiscoveryService mChannelDiscoveryService = new ChannelDiscoveryService ( this ) ;
2018-08-18 14:27:50 +00:00
private ShortcutService mShortcutService = new ShortcutService ( this ) ;
private AtomicBoolean mInitialAddressbookSyncCompleted = new AtomicBoolean ( false ) ;
private AtomicBoolean mForceForegroundService = new AtomicBoolean ( false ) ;
2019-01-13 10:27:13 +00:00
private AtomicBoolean mForceDuringOnCreate = new AtomicBoolean ( false ) ;
2020-04-15 20:40:37 +00:00
private AtomicReference < OngoingCall > ongoingCall = new AtomicReference < > ( ) ;
2018-08-18 14:27:50 +00:00
private OnMessagePacketReceived mMessageParser = new MessageParser ( this ) ;
private OnPresencePacketReceived mPresenceParser = new PresenceParser ( this ) ;
private IqParser mIqParser = new IqParser ( this ) ;
private MessageGenerator mMessageGenerator = new MessageGenerator ( this ) ;
public OnContactStatusChanged onContactStatusChanged = ( contact , online ) - > {
Conversation conversation = find ( getConversations ( ) , contact ) ;
if ( conversation ! = null ) {
if ( online ) {
if ( contact . getPresences ( ) . size ( ) = = 1 ) {
sendUnsentMessages ( conversation ) ;
}
}
}
} ;
private PresenceGenerator mPresenceGenerator = new PresenceGenerator ( this ) ;
private List < Account > accounts ;
2020-04-01 11:25:52 +00:00
private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager ( this ) ;
2018-10-19 21:29:17 +00:00
private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager ( this ) ;
2018-08-18 14:27:50 +00:00
private AvatarService mAvatarService = new AvatarService ( this ) ;
private MessageArchiveService mMessageArchiveService = new MessageArchiveService ( this ) ;
private PushManagementService mPushManagementService = new PushManagementService ( this ) ;
2018-10-19 21:29:17 +00:00
private QuickConversationsService mQuickConversationsService = new QuickConversationsService ( this ) ;
2018-08-18 14:27:50 +00:00
private final ConversationsFileObserver fileObserver = new ConversationsFileObserver (
Environment . getExternalStorageDirectory ( ) . getAbsolutePath ( )
) {
@Override
2020-07-30 10:55:19 +00:00
public void onEvent ( final int event , final File file ) {
markFileDeleted ( file ) ;
2018-08-18 14:27:50 +00:00
}
} ;
private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged ( ) {
@Override
public boolean onMessageAcknowledged ( Account account , String uuid ) {
for ( final Conversation conversation : getConversations ( ) ) {
if ( conversation . getAccount ( ) = = account ) {
Message message = conversation . findUnsentMessageWithUuid ( uuid ) ;
if ( message ! = null ) {
message . setStatus ( Message . STATUS_SEND ) ;
message . setErrorMessage ( null ) ;
databaseBackend . updateMessage ( message , false ) ;
return true ;
}
}
}
return false ;
}
} ;
2020-08-24 07:51:26 +00:00
private final AtomicBoolean isPhoneInCall = new AtomicBoolean ( false ) ;
private final PhoneStateListener phoneStateListener = new PhoneStateListener ( ) {
@Override
public void onCallStateChanged ( final int state , final String phoneNumber ) {
2020-08-24 10:47:54 +00:00
isPhoneInCall . set ( state ! = TelephonyManager . CALL_STATE_IDLE ) ;
if ( state = = TelephonyManager . CALL_STATE_OFFHOOK ) {
mJingleConnectionManager . notifyPhoneCallStarted ( ) ;
}
2020-08-24 07:51:26 +00:00
}
} ;
2018-08-18 14:27:50 +00:00
2019-01-12 09:21:21 +00:00
private boolean destroyed = false ;
2018-08-18 14:27:50 +00:00
private int unreadCount = - 1 ;
//Ui callback listeners
private final Set < OnConversationUpdate > mOnConversationUpdates = Collections . newSetFromMap ( new WeakHashMap < OnConversationUpdate , Boolean > ( ) ) ;
private final Set < OnShowErrorToast > mOnShowErrorToasts = Collections . newSetFromMap ( new WeakHashMap < OnShowErrorToast , Boolean > ( ) ) ;
private final Set < OnAccountUpdate > mOnAccountUpdates = Collections . newSetFromMap ( new WeakHashMap < OnAccountUpdate , Boolean > ( ) ) ;
private final Set < OnCaptchaRequested > mOnCaptchaRequested = Collections . newSetFromMap ( new WeakHashMap < OnCaptchaRequested , Boolean > ( ) ) ;
private final Set < OnRosterUpdate > mOnRosterUpdates = Collections . newSetFromMap ( new WeakHashMap < OnRosterUpdate , Boolean > ( ) ) ;
private final Set < OnUpdateBlocklist > mOnUpdateBlocklist = Collections . newSetFromMap ( new WeakHashMap < OnUpdateBlocklist , Boolean > ( ) ) ;
private final Set < OnMucRosterUpdate > mOnMucRosterUpdate = Collections . newSetFromMap ( new WeakHashMap < OnMucRosterUpdate , Boolean > ( ) ) ;
private final Set < OnKeyStatusUpdated > mOnKeyStatusUpdated = Collections . newSetFromMap ( new WeakHashMap < OnKeyStatusUpdated , Boolean > ( ) ) ;
2020-04-07 11:15:24 +00:00
private final Set < OnJingleRtpConnectionUpdate > onJingleRtpConnectionUpdate = Collections . newSetFromMap ( new WeakHashMap < OnJingleRtpConnectionUpdate , Boolean > ( ) ) ;
2018-08-18 14:27:50 +00:00
private final Object LISTENER_LOCK = new Object ( ) ;
2019-10-11 13:37:41 +00:00
public final Set < String > FILENAMES_TO_IGNORE_DELETION = new HashSet < > ( ) ;
2018-08-18 14:27:50 +00:00
private final OnBindListener mOnBindListener = new OnBindListener ( ) {
@Override
public void onBind ( final Account account ) {
synchronized ( mInProgressAvatarFetches ) {
for ( Iterator < String > iterator = mInProgressAvatarFetches . iterator ( ) ; iterator . hasNext ( ) ; ) {
final String KEY = iterator . next ( ) ;
if ( KEY . startsWith ( account . getJid ( ) . asBareJid ( ) + " _ " ) ) {
iterator . remove ( ) ;
}
}
}
2018-11-09 16:47:36 +00:00
boolean loggedInSuccessfully = account . setOption ( Account . OPTION_LOGGED_IN_SUCCESSFULLY , true ) ;
boolean gainedFeature = account . setOption ( Account . OPTION_HTTP_UPLOAD_AVAILABLE , account . getXmppConnection ( ) . getFeatures ( ) . httpUpload ( 0 ) ) ;
if ( loggedInSuccessfully | | gainedFeature ) {
2018-08-18 14:27:50 +00:00
databaseBackend . updateAccount ( account ) ;
}
2018-11-09 16:47:36 +00:00
if ( loggedInSuccessfully ) {
if ( ! TextUtils . isEmpty ( account . getDisplayName ( ) ) ) {
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : display name wasn't empty on first log in. publishing " ) ;
2018-11-09 16:47:36 +00:00
publishDisplayName ( account ) ;
}
}
2018-08-18 14:27:50 +00:00
account . getRoster ( ) . clearPresences ( ) ;
2019-06-30 17:54:07 +00:00
synchronized ( account . inProgressConferenceJoins ) {
account . inProgressConferenceJoins . clear ( ) ;
}
synchronized ( account . inProgressConferencePings ) {
account . inProgressConferencePings . clear ( ) ;
}
2020-04-13 16:30:12 +00:00
mJingleConnectionManager . notifyRebound ( ) ;
2018-11-07 14:44:39 +00:00
mQuickConversationsService . considerSyncBackground ( false ) ;
2018-08-18 14:27:50 +00:00
fetchRosterFromServer ( account ) ;
2019-09-27 22:32:29 +00:00
2019-09-27 23:21:19 +00:00
final XmppConnection connection = account . getXmppConnection ( ) ;
2019-09-27 22:32:29 +00:00
2019-09-27 23:21:19 +00:00
if ( connection . getFeatures ( ) . bookmarks2 ( ) ) {
fetchBookmarks2 ( account ) ;
} else if ( ! account . getXmppConnection ( ) . getFeatures ( ) . bookmarksConversion ( ) ) {
2018-08-18 14:27:50 +00:00
fetchBookmarks ( account ) ;
2019-09-27 23:21:19 +00:00
}
2018-08-18 14:27:50 +00:00
final boolean flexible = account . getXmppConnection ( ) . getFeatures ( ) . flexibleOfflineMessageRetrieval ( ) ;
final boolean catchup = getMessageArchiveService ( ) . inCatchup ( account ) ;
2018-12-05 18:11:40 +00:00
if ( flexible & & catchup & & account . getXmppConnection ( ) . isMamPreferenceAlways ( ) ) {
2018-08-18 14:27:50 +00:00
sendIqPacket ( account , mIqGenerator . purgeOfflineMessages ( ) , ( acc , packet ) - > {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
Log . d ( Config . LOGTAG , acc . getJid ( ) . asBareJid ( ) + " : successfully purged offline messages " ) ;
}
} ) ;
}
sendPresence ( account ) ;
if ( mPushManagementService . available ( account ) ) {
mPushManagementService . registerPushTokenOnServer ( account ) ;
}
connectMultiModeConversations ( account ) ;
syncDirtyContacts ( account ) ;
}
} ;
private AtomicLong mLastExpiryRun = new AtomicLong ( 0 ) ;
private SecureRandom mRandom ;
private LruCache < Pair < String , String > , ServiceDiscoveryResult > discoCache = new LruCache < > ( 20 ) ;
private OnStatusChanged statusListener = new OnStatusChanged ( ) {
@Override
public void onStatusChanged ( final Account account ) {
XmppConnection connection = account . getXmppConnection ( ) ;
updateAccountUi ( ) ;
2018-10-31 11:52:23 +00:00
if ( account . getStatus ( ) = = Account . State . ONLINE | | account . getStatus ( ) . isError ( ) ) {
mQuickConversationsService . signalAccountStateChange ( ) ;
}
2018-08-18 14:27:50 +00:00
if ( account . getStatus ( ) = = Account . State . ONLINE ) {
synchronized ( mLowPingTimeoutMode ) {
if ( mLowPingTimeoutMode . remove ( account . getJid ( ) . asBareJid ( ) ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : leaving low ping timeout mode " ) ;
}
}
if ( account . setShowErrorNotification ( true ) ) {
databaseBackend . updateAccount ( account ) ;
}
mMessageArchiveService . executePendingQueries ( account ) ;
if ( connection ! = null & & connection . getFeatures ( ) . csi ( ) ) {
if ( checkListeners ( ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " sending csi//inactive " ) ;
connection . sendInactive ( ) ;
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " sending csi//active " ) ;
connection . sendActive ( ) ;
}
}
List < Conversation > conversations = getConversations ( ) ;
for ( Conversation conversation : conversations ) {
2019-06-18 16:09:44 +00:00
final boolean inProgressJoin ;
synchronized ( account . inProgressConferenceJoins ) {
inProgressJoin = account . inProgressConferenceJoins . contains ( conversation ) ;
}
2019-06-30 19:57:37 +00:00
final boolean pendingJoin ;
synchronized ( account . pendingConferenceJoins ) {
pendingJoin = account . pendingConferenceJoins . contains ( conversation ) ;
}
2019-06-18 16:09:44 +00:00
if ( conversation . getAccount ( ) = = account
2019-06-30 19:57:37 +00:00
& & ! pendingJoin
2019-06-18 16:09:44 +00:00
& & ! inProgressJoin ) {
2018-08-18 14:27:50 +00:00
sendUnsentMessages ( conversation ) ;
}
}
2019-06-30 19:57:37 +00:00
final List < Conversation > pendingLeaves ;
synchronized ( account . pendingConferenceLeaves ) {
pendingLeaves = new ArrayList < > ( account . pendingConferenceLeaves ) ;
account . pendingConferenceLeaves . clear ( ) ;
}
for ( Conversation conversation : pendingLeaves ) {
2018-08-18 14:27:50 +00:00
leaveMuc ( conversation ) ;
}
2019-06-30 19:57:37 +00:00
final List < Conversation > pendingJoins ;
synchronized ( account . pendingConferenceJoins ) {
pendingJoins = new ArrayList < > ( account . pendingConferenceJoins ) ;
account . pendingConferenceJoins . clear ( ) ;
}
for ( Conversation conversation : pendingJoins ) {
2018-08-18 14:27:50 +00:00
joinMuc ( conversation ) ;
}
scheduleWakeUpCall ( Config . PING_MAX_INTERVAL , account . getUuid ( ) . hashCode ( ) ) ;
} else if ( account . getStatus ( ) = = Account . State . OFFLINE | | account . getStatus ( ) = = Account . State . DISABLED ) {
resetSendingToWaiting ( account ) ;
if ( account . isEnabled ( ) & & isInLowPingTimeoutMode ( account ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : went into offline state during low ping mode. reconnecting now " ) ;
reconnectAccount ( account , true , false ) ;
} else {
int timeToReconnect = mRandom . nextInt ( 10 ) + 2 ;
scheduleWakeUpCall ( timeToReconnect , account . getUuid ( ) . hashCode ( ) ) ;
}
} else if ( account . getStatus ( ) = = Account . State . REGISTRATION_SUCCESSFUL ) {
databaseBackend . updateAccount ( account ) ;
reconnectAccount ( account , true , false ) ;
} else if ( account . getStatus ( ) ! = Account . State . CONNECTING & & account . getStatus ( ) ! = Account . State . NO_INTERNET ) {
resetSendingToWaiting ( account ) ;
if ( connection ! = null & & account . getStatus ( ) . isAttemptReconnect ( ) ) {
final int next = connection . getTimeToNextAttempt ( ) ;
final boolean lowPingTimeoutMode = isInLowPingTimeoutMode ( account ) ;
if ( next < = 0 ) {
2019-08-14 16:44:57 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : error connecting account. reconnecting now. lowPingTimeout= " + lowPingTimeoutMode ) ;
2018-08-18 14:27:50 +00:00
reconnectAccount ( account , true , false ) ;
} else {
final int attempt = connection . getAttempt ( ) + 1 ;
2019-08-14 16:44:57 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : error connecting account. try again in " + next + " s for the " + attempt + " time. lowPingTimeout= " + lowPingTimeoutMode ) ;
2018-08-18 14:27:50 +00:00
scheduleWakeUpCall ( next , account . getUuid ( ) . hashCode ( ) ) ;
}
}
}
getNotificationService ( ) . updateErrorNotification ( ) ;
}
} ;
private OpenPgpServiceConnection pgpServiceConnection ;
private PgpEngine mPgpEngine = null ;
private WakeLock wakeLock ;
private PowerManager pm ;
private LruCache < String , Bitmap > mBitmapCache ;
2018-09-06 21:17:37 +00:00
private BroadcastReceiver mInternalEventReceiver = new InternalEventReceiver ( ) ;
private BroadcastReceiver mInternalScreenEventReceiver = new InternalEventReceiver ( ) ;
2018-08-18 14:27:50 +00:00
private static String generateFetchKey ( Account account , final Avatar avatar ) {
return account . getJid ( ) . asBareJid ( ) + " _ " + avatar . owner + " _ " + avatar . sha1sum ;
}
private boolean isInLowPingTimeoutMode ( Account account ) {
synchronized ( mLowPingTimeoutMode ) {
return mLowPingTimeoutMode . contains ( account . getJid ( ) . asBareJid ( ) ) ;
}
}
public void startForcingForegroundNotification ( ) {
mForceForegroundService . set ( true ) ;
toggleForegroundService ( ) ;
}
public void stopForcingForegroundNotification ( ) {
mForceForegroundService . set ( false ) ;
toggleForegroundService ( ) ;
}
public boolean areMessagesInitialized ( ) {
return this . restoredFromDatabaseLatch . getCount ( ) = = 0 ;
}
public PgpEngine getPgpEngine ( ) {
if ( ! Config . supportOpenPgp ( ) ) {
return null ;
} else if ( pgpServiceConnection ! = null & & pgpServiceConnection . isBound ( ) ) {
if ( this . mPgpEngine = = null ) {
this . mPgpEngine = new PgpEngine ( new OpenPgpApi (
getApplicationContext ( ) ,
pgpServiceConnection . getService ( ) ) , this ) ;
}
return mPgpEngine ;
} else {
return null ;
}
2016-11-08 11:20:07 +00:00
2018-08-18 14:27:50 +00:00
}
2014-05-01 20:33:49 +00:00
2018-08-18 14:27:50 +00:00
public OpenPgpApi getOpenPgpApi ( ) {
if ( ! Config . supportOpenPgp ( ) ) {
return null ;
} else if ( pgpServiceConnection ! = null & & pgpServiceConnection . isBound ( ) ) {
return new OpenPgpApi ( this , pgpServiceConnection . getService ( ) ) ;
} else {
return null ;
}
}
2015-10-15 22:21:47 +00:00
2018-08-18 14:27:50 +00:00
public FileBackend getFileBackend ( ) {
return this . fileBackend ;
}
2014-03-11 14:44:22 +00:00
2018-08-18 14:27:50 +00:00
public AvatarService getAvatarService ( ) {
return this . mAvatarService ;
}
2016-11-08 11:20:07 +00:00
2018-08-18 14:27:50 +00:00
public void attachLocationToConversation ( final Conversation conversation , final Uri uri , final UiCallback < Message > callback ) {
int encryption = conversation . getNextEncryption ( ) ;
if ( encryption = = Message . ENCRYPTION_PGP ) {
encryption = Message . ENCRYPTION_DECRYPTED ;
}
Message message = new Message ( conversation , uri . toString ( ) , encryption ) ;
2019-04-27 09:46:43 +00:00
Message . configurePrivateMessage ( message ) ;
2018-08-18 14:27:50 +00:00
if ( encryption = = Message . ENCRYPTION_DECRYPTED ) {
getPgpEngine ( ) . encrypt ( message , callback ) ;
} else {
sendMessage ( message ) ;
callback . success ( message ) ;
}
}
2015-10-07 22:35:04 +00:00
2018-08-18 14:27:50 +00:00
public void attachFileToConversation ( final Conversation conversation , final Uri uri , final String type , final UiCallback < Message > callback ) {
final Message message ;
if ( conversation . getNextEncryption ( ) = = Message . ENCRYPTION_PGP ) {
message = new Message ( conversation , " " , Message . ENCRYPTION_DECRYPTED ) ;
} else {
message = new Message ( conversation , " " , conversation . getNextEncryption ( ) ) ;
}
2019-04-27 09:46:43 +00:00
if ( ! Message . configurePrivateFileMessage ( message ) ) {
message . setCounterpart ( conversation . getNextCounterpart ( ) ) ;
message . setType ( Message . TYPE_FILE ) ;
}
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , " attachFile: type= " + message . getType ( ) ) ;
Log . d ( Config . LOGTAG , " counterpart= " + message . getCounterpart ( ) ) ;
2018-08-18 14:27:50 +00:00
final AttachFileToConversationRunnable runnable = new AttachFileToConversationRunnable ( this , uri , type , message , callback ) ;
if ( runnable . isVideoMessage ( ) ) {
mVideoCompressionExecutor . execute ( runnable ) ;
} else {
mFileAddingExecutor . execute ( runnable ) ;
}
}
2015-10-14 20:55:59 +00:00
2018-08-18 14:27:50 +00:00
public void attachImageToConversation ( final Conversation conversation , final Uri uri , final UiCallback < Message > callback ) {
final String mimeType = MimeUtils . guessMimeTypeFromUri ( this , uri ) ;
final String compressPictures = getCompressPicturesPreference ( ) ;
2018-06-24 18:54:01 +00:00
2018-08-18 14:27:50 +00:00
if ( " never " . equals ( compressPictures )
| | ( " auto " . equals ( compressPictures ) & & getFileBackend ( ) . useImageAsIs ( uri ) )
2018-09-22 14:51:00 +00:00
| | ( mimeType ! = null & & mimeType . endsWith ( " /gif " ) )
| | getFileBackend ( ) . unusualBounds ( uri ) ) {
2018-08-18 14:27:50 +00:00
Log . d ( Config . LOGTAG , conversation . getAccount ( ) . getJid ( ) . asBareJid ( ) + " : not compressing picture. sending as file " ) ;
attachFileToConversation ( conversation , uri , mimeType , callback ) ;
return ;
}
final Message message ;
if ( conversation . getNextEncryption ( ) = = Message . ENCRYPTION_PGP ) {
message = new Message ( conversation , " " , Message . ENCRYPTION_DECRYPTED ) ;
} else {
message = new Message ( conversation , " " , conversation . getNextEncryption ( ) ) ;
}
2019-04-27 09:46:43 +00:00
if ( ! Message . configurePrivateFileMessage ( message ) ) {
message . setCounterpart ( conversation . getNextCounterpart ( ) ) ;
message . setType ( Message . TYPE_IMAGE ) ;
}
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , " attachImage: type= " + message . getType ( ) ) ;
2018-08-18 14:27:50 +00:00
mFileAddingExecutor . execute ( ( ) - > {
try {
getFileBackend ( ) . copyImageToPrivateStorage ( message , uri ) ;
2020-06-09 19:08:27 +00:00
} catch ( FileBackend . NotAnImageFileException e ) {
attachFileToConversation ( conversation , uri , mimeType , callback ) ;
return ;
2018-08-18 14:27:50 +00:00
} catch ( final FileBackend . FileCopyException e ) {
callback . error ( e . getResId ( ) , message ) ;
2020-06-09 19:08:27 +00:00
return ;
}
if ( conversation . getNextEncryption ( ) = = Message . ENCRYPTION_PGP ) {
final PgpEngine pgpEngine = getPgpEngine ( ) ;
if ( pgpEngine ! = null ) {
pgpEngine . encrypt ( message , callback ) ;
} else if ( callback ! = null ) {
callback . error ( R . string . unable_to_connect_to_keychain , null ) ;
}
} else {
sendMessage ( message ) ;
callback . success ( message ) ;
2018-08-18 14:27:50 +00:00
}
} ) ;
}
public Conversation find ( Bookmark bookmark ) {
return find ( bookmark . getAccount ( ) , bookmark . getJid ( ) ) ;
}
public Conversation find ( final Account account , final Jid jid ) {
return find ( getConversations ( ) , account , jid ) ;
}
2018-08-28 13:04:55 +00:00
public boolean isMuc ( final Account account , final Jid jid ) {
final Conversation c = find ( account , jid ) ;
return c ! = null & & c . getMode ( ) = = Conversational . MODE_MULTI ;
}
2020-08-29 06:16:08 +00:00
public void search ( final List < String > term , final String uuid , final OnSearchResultsAvailable onSearchResultsAvailable ) {
MessageSearchTask . search ( this , term , uuid , onSearchResultsAvailable ) ;
2018-08-18 14:27:50 +00:00
}
@Override
public int onStartCommand ( Intent intent , int flags , int startId ) {
final String action = intent = = null ? null : intent . getAction ( ) ;
2018-11-17 11:57:36 +00:00
final boolean needsForegroundService = intent ! = null & & intent . getBooleanExtra ( EventReceiver . EXTRA_NEEDS_FOREGROUND_SERVICE , false ) ;
if ( needsForegroundService ) {
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , " toggle forced foreground service after receiving event (action= " + action + " ) " ) ;
2018-11-17 11:57:36 +00:00
toggleForegroundService ( true ) ;
}
2018-08-18 14:27:50 +00:00
String pushedAccountHash = null ;
boolean interactive = false ;
if ( action ! = null ) {
final String uuid = intent . getStringExtra ( " uuid " ) ;
switch ( action ) {
case ConnectivityManager . CONNECTIVITY_ACTION :
2019-01-27 19:54:45 +00:00
if ( hasInternetConnection ( ) ) {
if ( Config . POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0 ) {
schedulePostConnectivityChange ( ) ;
}
if ( Config . RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE ) {
resetAllAttemptCounts ( true , false ) ;
}
2018-08-18 14:27:50 +00:00
}
break ;
case Intent . ACTION_SHUTDOWN :
logoutAndSave ( true ) ;
return START_NOT_STICKY ;
case ACTION_CLEAR_NOTIFICATION :
mNotificationExecutor . execute ( ( ) - > {
try {
final Conversation c = findConversationByUuid ( uuid ) ;
if ( c ! = null ) {
mNotificationService . clear ( c ) ;
} else {
mNotificationService . clear ( ) ;
}
restoredFromDatabaseLatch . await ( ) ;
} catch ( InterruptedException e ) {
Log . d ( Config . LOGTAG , " unable to process clear notification " ) ;
}
} ) ;
break ;
2020-04-10 13:19:56 +00:00
case ACTION_DISMISS_CALL : {
2020-04-07 16:50:39 +00:00
final String sessionId = intent . getStringExtra ( RtpSessionActivity . EXTRA_SESSION_ID ) ;
2020-04-10 13:19:56 +00:00
Log . d ( Config . LOGTAG , " received intent to dismiss call with session id " + sessionId ) ;
2020-04-07 19:26:51 +00:00
mJingleConnectionManager . rejectRtpSession ( sessionId ) ;
2020-08-19 11:57:33 +00:00
break ;
2020-04-10 13:19:56 +00:00
}
2020-08-19 11:57:33 +00:00
case TorServiceUtils . ACTION_STATUS :
final String status = intent . getStringExtra ( TorServiceUtils . EXTRA_STATUS ) ;
if ( " ON " . equals ( status ) ) {
handleOrbotStartedEvent ( ) ;
return START_STICKY ;
}
break ;
2020-04-10 13:19:56 +00:00
case ACTION_END_CALL : {
final String sessionId = intent . getStringExtra ( RtpSessionActivity . EXTRA_SESSION_ID ) ;
Log . d ( Config . LOGTAG , " received intent to end call with session id " + sessionId ) ;
mJingleConnectionManager . endRtpSession ( sessionId ) ;
}
2020-04-13 10:02:34 +00:00
break ;
2020-06-21 13:40:51 +00:00
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 ;
}
2018-08-18 14:27:50 +00:00
case ACTION_DISMISS_ERROR_NOTIFICATIONS :
dismissErrorNotifications ( ) ;
break ;
case ACTION_TRY_AGAIN :
resetAllAttemptCounts ( false , true ) ;
interactive = true ;
break ;
case ACTION_REPLY_TO_CONVERSATION :
Bundle remoteInput = RemoteInput . getResultsFromIntent ( intent ) ;
if ( remoteInput = = null ) {
break ;
}
final CharSequence body = remoteInput . getCharSequence ( " text_reply " ) ;
final boolean dismissNotification = intent . getBooleanExtra ( " dismiss_notification " , false ) ;
if ( body = = null | | body . length ( ) < = 0 ) {
break ;
}
mNotificationExecutor . execute ( ( ) - > {
try {
restoredFromDatabaseLatch . await ( ) ;
final Conversation c = findConversationByUuid ( uuid ) ;
if ( c ! = null ) {
directReply ( c , body . toString ( ) , dismissNotification ) ;
}
} catch ( InterruptedException e ) {
Log . d ( Config . LOGTAG , " unable to process direct reply " ) ;
}
} ) ;
break ;
case ACTION_MARK_AS_READ :
mNotificationExecutor . execute ( ( ) - > {
final Conversation c = findConversationByUuid ( uuid ) ;
if ( c = = null ) {
Log . d ( Config . LOGTAG , " received mark read intent for unknown conversation ( " + uuid + " ) " ) ;
return ;
}
try {
restoredFromDatabaseLatch . await ( ) ;
sendReadMarker ( c , null ) ;
} catch ( InterruptedException e ) {
Log . d ( Config . LOGTAG , " unable to process notification read marker for conversation " + c . getName ( ) ) ;
}
} ) ;
break ;
case ACTION_SNOOZE :
mNotificationExecutor . execute ( ( ) - > {
final Conversation c = findConversationByUuid ( uuid ) ;
if ( c = = null ) {
Log . d ( Config . LOGTAG , " received snooze intent for unknown conversation ( " + uuid + " ) " ) ;
return ;
}
c . setMutedTill ( System . currentTimeMillis ( ) + 30 * 60 * 1000 ) ;
mNotificationService . clear ( c ) ;
updateConversation ( c ) ;
} ) ;
case AudioManager . RINGER_MODE_CHANGED_ACTION :
2018-09-06 21:17:37 +00:00
case NotificationManager . ACTION_INTERRUPTION_FILTER_CHANGED :
2018-08-18 14:27:50 +00:00
if ( dndOnSilentMode ( ) ) {
refreshAllPresences ( ) ;
}
break ;
case Intent . ACTION_SCREEN_ON :
deactivateGracePeriod ( ) ;
case Intent . ACTION_SCREEN_OFF :
if ( awayWhenScreenOff ( ) ) {
refreshAllPresences ( ) ;
}
break ;
case ACTION_FCM_TOKEN_REFRESH :
refreshAllFcmTokens ( ) ;
break ;
case ACTION_IDLE_PING :
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . M ) {
scheduleNextIdlePing ( ) ;
}
break ;
case ACTION_FCM_MESSAGE_RECEIVED :
pushedAccountHash = intent . getStringExtra ( " account " ) ;
Log . d ( Config . LOGTAG , " push message arrived in service. account= " + pushedAccountHash ) ;
break ;
case Intent . ACTION_SEND :
Uri uri = intent . getData ( ) ;
if ( uri ! = null ) {
Log . d ( Config . LOGTAG , " received uri permission for " + uri . toString ( ) ) ;
}
return START_STICKY ;
}
}
synchronized ( this ) {
WakeLockHelper . acquire ( wakeLock ) ;
2019-01-27 19:54:45 +00:00
boolean pingNow = ConnectivityManager . CONNECTIVITY_ACTION . equals ( action ) | | ( Config . POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0 & & ACTION_POST_CONNECTIVITY_CHANGE . equals ( action ) ) ;
2019-06-26 15:40:05 +00:00
final HashSet < Account > pingCandidates = new HashSet < > ( ) ;
final String androidId = PhoneHelper . getAndroidId ( this ) ;
2018-08-18 14:27:50 +00:00
for ( Account account : accounts ) {
2019-06-26 15:40:05 +00:00
final boolean pushWasMeantForThisAccount = CryptoHelper . getAccountFingerprint ( account , androidId ) . equals ( pushedAccountHash ) ;
2018-08-18 14:27:50 +00:00
pingNow | = processAccountState ( account ,
interactive ,
" ui " . equals ( action ) ,
2019-06-26 15:40:05 +00:00
pushWasMeantForThisAccount ,
2018-08-18 14:27:50 +00:00
pingCandidates ) ;
}
if ( pingNow ) {
for ( Account account : pingCandidates ) {
final boolean lowTimeout = isInLowPingTimeoutMode ( account ) ;
account . getXmppConnection ( ) . sendPing ( ) ;
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " send ping (action= " + action + " ,lowTimeout= " + Boolean . toString ( lowTimeout ) + " ) " ) ;
scheduleWakeUpCall ( lowTimeout ? Config . LOW_PING_TIMEOUT : Config . PING_TIMEOUT , account . getUuid ( ) . hashCode ( ) ) ;
}
}
WakeLockHelper . release ( wakeLock ) ;
}
if ( SystemClock . elapsedRealtime ( ) - mLastExpiryRun . get ( ) > = Config . EXPIRY_INTERVAL ) {
expireOldMessages ( ) ;
}
return START_STICKY ;
}
2020-08-19 11:57:33 +00:00
private void handleOrbotStartedEvent ( ) {
for ( final Account account : accounts ) {
if ( account . getStatus ( ) = = Account . State . TOR_NOT_AVAILABLE ) {
reconnectAccount ( account , true , false ) ;
}
}
}
2018-08-18 14:27:50 +00:00
private boolean processAccountState ( Account account , boolean interactive , boolean isUiAction , boolean isAccountPushed , HashSet < Account > pingCandidates ) {
boolean pingNow = false ;
if ( account . getStatus ( ) . isAttemptReconnect ( ) ) {
if ( ! hasInternetConnection ( ) ) {
account . setStatus ( Account . State . NO_INTERNET ) ;
if ( statusListener ! = null ) {
statusListener . onStatusChanged ( account ) ;
}
} else {
if ( account . getStatus ( ) = = Account . State . NO_INTERNET ) {
account . setStatus ( Account . State . OFFLINE ) ;
if ( statusListener ! = null ) {
statusListener . onStatusChanged ( account ) ;
}
}
if ( account . getStatus ( ) = = Account . State . ONLINE ) {
synchronized ( mLowPingTimeoutMode ) {
long lastReceived = account . getXmppConnection ( ) . getLastPacketReceived ( ) ;
long lastSent = account . getXmppConnection ( ) . getLastPingSent ( ) ;
long pingInterval = isUiAction ? Config . PING_MIN_INTERVAL * 1000 : Config . PING_MAX_INTERVAL * 1000 ;
long msToNextPing = ( Math . max ( lastReceived , lastSent ) + pingInterval ) - SystemClock . elapsedRealtime ( ) ;
int pingTimeout = mLowPingTimeoutMode . contains ( account . getJid ( ) . asBareJid ( ) ) ? Config . LOW_PING_TIMEOUT * 1000 : Config . PING_TIMEOUT * 1000 ;
long pingTimeoutIn = ( lastSent + pingTimeout ) - SystemClock . elapsedRealtime ( ) ;
if ( lastSent > lastReceived ) {
if ( pingTimeoutIn < 0 ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : ping timeout " ) ;
this . reconnectAccount ( account , true , interactive ) ;
} else {
int secs = ( int ) ( pingTimeoutIn / 1000 ) ;
this . scheduleWakeUpCall ( secs , account . getUuid ( ) . hashCode ( ) ) ;
}
} else {
pingCandidates . add ( account ) ;
if ( isAccountPushed ) {
pingNow = true ;
if ( mLowPingTimeoutMode . add ( account . getJid ( ) . asBareJid ( ) ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : entering low ping timeout mode " ) ;
}
} else if ( msToNextPing < = 0 ) {
pingNow = true ;
} else {
this . scheduleWakeUpCall ( ( int ) ( msToNextPing / 1000 ) , account . getUuid ( ) . hashCode ( ) ) ;
if ( mLowPingTimeoutMode . remove ( account . getJid ( ) . asBareJid ( ) ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : leaving low ping timeout mode " ) ;
}
}
}
}
} else if ( account . getStatus ( ) = = Account . State . OFFLINE ) {
reconnectAccount ( account , true , interactive ) ;
} else if ( account . getStatus ( ) = = Account . State . CONNECTING ) {
long secondsSinceLastConnect = ( SystemClock . elapsedRealtime ( ) - account . getXmppConnection ( ) . getLastConnect ( ) ) / 1000 ;
long secondsSinceLastDisco = ( SystemClock . elapsedRealtime ( ) - account . getXmppConnection ( ) . getLastDiscoStarted ( ) ) / 1000 ;
long discoTimeout = Config . CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco ;
long timeout = Config . CONNECT_TIMEOUT - secondsSinceLastConnect ;
if ( timeout < 0 ) {
Log . d ( Config . LOGTAG , account . getJid ( ) + " : time out during connect reconnecting (secondsSinceLast= " + secondsSinceLastConnect + " ) " ) ;
account . getXmppConnection ( ) . resetAttemptCount ( false ) ;
reconnectAccount ( account , true , interactive ) ;
} else if ( discoTimeout < 0 ) {
account . getXmppConnection ( ) . sendDiscoTimeout ( ) ;
scheduleWakeUpCall ( ( int ) Math . min ( timeout , discoTimeout ) , account . getUuid ( ) . hashCode ( ) ) ;
} else {
scheduleWakeUpCall ( ( int ) Math . min ( timeout , discoTimeout ) , account . getUuid ( ) . hashCode ( ) ) ;
}
} else {
if ( account . getXmppConnection ( ) . getTimeToNextAttempt ( ) < = 0 ) {
reconnectAccount ( account , true , interactive ) ;
}
}
}
}
return pingNow ;
}
2019-04-25 19:10:50 +00:00
public void reinitializeMuclumbusService ( ) {
mChannelDiscoveryService . initializeMuclumbusService ( ) ;
}
2019-11-02 08:43:37 +00:00
public void discoverChannels ( String query , ChannelDiscoveryService . Method method , ChannelDiscoveryService . OnChannelSearchResultsFound onChannelSearchResultsFound ) {
mChannelDiscoveryService . discover ( Strings . nullToEmpty ( query ) . trim ( ) , method , onChannelSearchResultsFound ) ;
2019-03-31 15:12:01 +00:00
}
2018-08-18 14:27:50 +00:00
public boolean isDataSaverDisabled ( ) {
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . N ) {
ConnectivityManager connectivityManager = ( ConnectivityManager ) getSystemService ( CONNECTIVITY_SERVICE ) ;
return ! connectivityManager . isActiveNetworkMetered ( )
| | connectivityManager . getRestrictBackgroundStatus ( ) = = ConnectivityManager . RESTRICT_BACKGROUND_STATUS_DISABLED ;
} else {
return true ;
}
}
private void directReply ( Conversation conversation , String body , final boolean dismissAfterReply ) {
Message message = new Message ( conversation , body , conversation . getNextEncryption ( ) ) ;
message . markUnread ( ) ;
if ( message . getEncryption ( ) = = Message . ENCRYPTION_PGP ) {
getPgpEngine ( ) . encrypt ( message , new UiCallback < Message > ( ) {
@Override
public void success ( Message message ) {
if ( dismissAfterReply ) {
markRead ( ( Conversation ) message . getConversation ( ) , true ) ;
} else {
mNotificationService . pushFromDirectReply ( message ) ;
}
}
@Override
public void error ( int errorCode , Message object ) {
}
@Override
2019-06-26 15:40:05 +00:00
public void userInputRequired ( PendingIntent pi , Message object ) {
2018-08-18 14:27:50 +00:00
}
} ) ;
} else {
sendMessage ( message ) ;
if ( dismissAfterReply ) {
markRead ( conversation , true ) ;
} else {
mNotificationService . pushFromDirectReply ( message ) ;
}
}
}
private boolean dndOnSilentMode ( ) {
return getBooleanPreference ( SettingsActivity . DND_ON_SILENT_MODE , R . bool . dnd_on_silent_mode ) ;
}
private boolean manuallyChangePresence ( ) {
return getBooleanPreference ( SettingsActivity . MANUALLY_CHANGE_PRESENCE , R . bool . manually_change_presence ) ;
}
private boolean treatVibrateAsSilent ( ) {
return getBooleanPreference ( SettingsActivity . TREAT_VIBRATE_AS_SILENT , R . bool . treat_vibrate_as_silent ) ;
}
private boolean awayWhenScreenOff ( ) {
return getBooleanPreference ( SettingsActivity . AWAY_WHEN_SCREEN_IS_OFF , R . bool . away_when_screen_off ) ;
}
private String getCompressPicturesPreference ( ) {
return getPreferences ( ) . getString ( " picture_compression " , getResources ( ) . getString ( R . string . picture_compression ) ) ;
}
private Presence . Status getTargetPresence ( ) {
if ( dndOnSilentMode ( ) & & isPhoneSilenced ( ) ) {
return Presence . Status . DND ;
} else if ( awayWhenScreenOff ( ) & & ! isInteractive ( ) ) {
return Presence . Status . AWAY ;
} else {
return Presence . Status . ONLINE ;
}
}
@SuppressLint ( " NewApi " )
@SuppressWarnings ( " deprecation " )
public boolean isInteractive ( ) {
2018-11-22 09:06:56 +00:00
try {
final PowerManager pm = ( PowerManager ) getSystemService ( Context . POWER_SERVICE ) ;
2018-08-18 14:27:50 +00:00
2018-11-22 09:06:56 +00:00
final boolean isScreenOn ;
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . LOLLIPOP ) {
isScreenOn = pm . isScreenOn ( ) ;
} else {
isScreenOn = pm . isInteractive ( ) ;
}
return isScreenOn ;
} catch ( RuntimeException e ) {
return false ;
2018-08-18 14:27:50 +00:00
}
}
private boolean isPhoneSilenced ( ) {
2018-09-06 21:17:37 +00:00
final boolean notificationDnd ;
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . M ) {
final NotificationManager notificationManager = getSystemService ( NotificationManager . class ) ;
final int filter = notificationManager = = null ? NotificationManager . INTERRUPTION_FILTER_UNKNOWN : notificationManager . getCurrentInterruptionFilter ( ) ;
notificationDnd = filter > = NotificationManager . INTERRUPTION_FILTER_PRIORITY ;
} else {
notificationDnd = false ;
}
final AudioManager audioManager = ( AudioManager ) getSystemService ( Context . AUDIO_SERVICE ) ;
final int ringerMode = audioManager = = null ? AudioManager . RINGER_MODE_NORMAL : audioManager . getRingerMode ( ) ;
2018-08-18 14:27:50 +00:00
try {
if ( treatVibrateAsSilent ( ) ) {
2018-09-06 21:17:37 +00:00
return notificationDnd | | ringerMode ! = AudioManager . RINGER_MODE_NORMAL ;
2018-08-18 14:27:50 +00:00
} else {
2018-09-06 21:17:37 +00:00
return notificationDnd | | ringerMode = = AudioManager . RINGER_MODE_SILENT ;
2018-08-18 14:27:50 +00:00
}
} catch ( Throwable throwable ) {
Log . d ( Config . LOGTAG , " platform bug in isPhoneSilenced ( " + throwable . getMessage ( ) + " ) " ) ;
2018-09-06 21:17:37 +00:00
return notificationDnd ;
2018-08-18 14:27:50 +00:00
}
}
private void resetAllAttemptCounts ( boolean reallyAll , boolean retryImmediately ) {
Log . d ( Config . LOGTAG , " resetting all attempt counts " ) ;
for ( Account account : accounts ) {
if ( account . hasErrorStatus ( ) | | reallyAll ) {
final XmppConnection connection = account . getXmppConnection ( ) ;
if ( connection ! = null ) {
connection . resetAttemptCount ( retryImmediately ) ;
}
}
if ( account . setShowErrorNotification ( true ) ) {
2019-04-17 10:49:36 +00:00
mDatabaseWriterExecutor . execute ( ( ) - > databaseBackend . updateAccount ( account ) ) ;
2018-08-18 14:27:50 +00:00
}
}
mNotificationService . updateErrorNotification ( ) ;
}
private void dismissErrorNotifications ( ) {
for ( final Account account : this . accounts ) {
if ( account . hasErrorStatus ( ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : dismissing error notification " ) ;
if ( account . setShowErrorNotification ( false ) ) {
2019-04-17 10:49:36 +00:00
mDatabaseWriterExecutor . execute ( ( ) - > databaseBackend . updateAccount ( account ) ) ;
2018-08-18 14:27:50 +00:00
}
}
}
}
private void expireOldMessages ( ) {
expireOldMessages ( false ) ;
}
public void expireOldMessages ( final boolean resetHasMessagesLeftOnServer ) {
mLastExpiryRun . set ( SystemClock . elapsedRealtime ( ) ) ;
mDatabaseWriterExecutor . execute ( ( ) - > {
long timestamp = getAutomaticMessageDeletionDate ( ) ;
if ( timestamp > 0 ) {
databaseBackend . expireOldMessages ( timestamp ) ;
synchronized ( XmppConnectionService . this . conversations ) {
for ( Conversation conversation : XmppConnectionService . this . conversations ) {
conversation . expireOldMessages ( timestamp ) ;
if ( resetHasMessagesLeftOnServer ) {
conversation . messagesLoaded . set ( true ) ;
conversation . setHasMessagesLeftOnServer ( true ) ;
}
}
}
updateConversationUi ( ) ;
}
} ) ;
}
public boolean hasInternetConnection ( ) {
final ConnectivityManager cm = ( ConnectivityManager ) getSystemService ( Context . CONNECTIVITY_SERVICE ) ;
try {
final NetworkInfo activeNetwork = cm = = null ? null : cm . getActiveNetworkInfo ( ) ;
2018-09-06 21:18:06 +00:00
return activeNetwork ! = null & & ( activeNetwork . isConnected ( ) | | activeNetwork . getType ( ) = = ConnectivityManager . TYPE_ETHERNET ) ;
2018-08-18 14:27:50 +00:00
} catch ( RuntimeException e ) {
Log . d ( Config . LOGTAG , " unable to check for internet connection " , e ) ;
return true ; //if internet connection can not be checked it is probably best to just try
}
}
@SuppressLint ( " TrulyRandom " )
@Override
public void onCreate ( ) {
2019-01-13 10:27:13 +00:00
if ( Compatibility . runsTwentySix ( ) ) {
mNotificationService . initializeChannels ( ) ;
}
2019-04-25 19:10:50 +00:00
mChannelDiscoveryService . initializeMuclumbusService ( ) ;
2019-01-13 10:27:13 +00:00
mForceDuringOnCreate . set ( Compatibility . runsAndTargetsTwentySix ( this ) ) ;
toggleForegroundService ( ) ;
2019-01-12 09:21:21 +00:00
this . destroyed = false ;
2018-08-18 14:27:50 +00:00
OmemoSetting . load ( this ) ;
ExceptionHelper . init ( getApplicationContext ( ) ) ;
2018-10-06 17:33:38 +00:00
try {
Security . insertProviderAt ( Conscrypt . newProvider ( ) , 1 ) ;
} catch ( Throwable throwable ) {
2019-10-07 07:51:03 +00:00
Log . e ( Config . LOGTAG , " unable to initialize security provider " , throwable ) ;
2018-10-06 17:33:38 +00:00
}
2018-08-18 14:27:50 +00:00
Resolver . init ( this ) ;
this . mRandom = new SecureRandom ( ) ;
updateMemorizingTrustmanager ( ) ;
final int maxMemory = ( int ) ( Runtime . getRuntime ( ) . maxMemory ( ) / 1024 ) ;
final int cacheSize = maxMemory / 8 ;
this . mBitmapCache = new LruCache < String , Bitmap > ( cacheSize ) {
@Override
protected int sizeOf ( final String key , final Bitmap bitmap ) {
return bitmap . getByteCount ( ) / 1024 ;
}
} ;
if ( mLastActivity = = 0 ) {
mLastActivity = getPreferences ( ) . getLong ( SETTING_LAST_ACTIVITY_TS , System . currentTimeMillis ( ) ) ;
}
2014-02-05 21:33:39 +00:00
2018-08-18 14:27:50 +00:00
Log . d ( Config . LOGTAG , " initializing database... " ) ;
this . databaseBackend = DatabaseBackend . getInstance ( getApplicationContext ( ) ) ;
Log . d ( Config . LOGTAG , " restoring accounts... " ) ;
this . accounts = databaseBackend . getAccounts ( ) ;
final SharedPreferences . Editor editor = getPreferences ( ) . edit ( ) ;
if ( this . accounts . size ( ) = = 0 & & Arrays . asList ( " Sony " , " Sony Ericsson " ) . contains ( Build . MANUFACTURER ) ) {
editor . putBoolean ( SettingsActivity . KEEP_FOREGROUND_SERVICE , true ) ;
Log . d ( Config . LOGTAG , Build . MANUFACTURER + " is on blacklist. enabling foreground service " ) ;
}
2019-01-13 14:28:24 +00:00
final boolean hasEnabledAccounts = hasEnabledAccounts ( ) ;
editor . putBoolean ( EventReceiver . SETTING_ENABLED_ACCOUNTS , hasEnabledAccounts ) . apply ( ) ;
2018-08-18 14:27:50 +00:00
editor . apply ( ) ;
2019-01-13 14:28:24 +00:00
toggleSetProfilePictureActivity ( hasEnabledAccounts ) ;
2014-05-22 07:36:00 +00:00
2018-08-18 14:27:50 +00:00
restoreFromDatabase ( ) ;
2016-11-08 11:20:07 +00:00
2018-09-05 19:37:05 +00:00
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . M | | ContextCompat . checkSelfPermission ( this , Manifest . permission . READ_CONTACTS ) = = PackageManager . PERMISSION_GRANTED ) {
2018-09-06 13:55:54 +00:00
startContactObserver ( ) ;
2018-09-05 19:37:05 +00:00
}
2018-09-17 19:24:25 +00:00
if ( Compatibility . hasStoragePermission ( this ) ) {
2018-08-18 14:27:50 +00:00
Log . d ( Config . LOGTAG , " starting file observer " ) ;
2019-01-12 09:21:21 +00:00
mFileAddingExecutor . execute ( this . fileObserver : : startWatching ) ;
2019-01-10 13:52:27 +00:00
mFileAddingExecutor . execute ( this : : checkForDeletedFiles ) ;
2018-08-18 14:27:50 +00:00
}
if ( Config . supportOpenPgp ( ) ) {
this . pgpServiceConnection = new OpenPgpServiceConnection ( this , " org.sufficientlysecure.keychain " , new OpenPgpServiceConnection . OnBound ( ) {
@Override
public void onBound ( IOpenPgpService2 service ) {
for ( Account account : accounts ) {
final PgpDecryptionService pgp = account . getPgpDecryptionService ( ) ;
if ( pgp ! = null ) {
pgp . continueDecryption ( true ) ;
}
}
}
@Override
public void onError ( Exception e ) {
}
} ) ;
this . pgpServiceConnection . bindToService ( ) ;
}
2014-05-22 07:36:00 +00:00
2018-08-18 14:27:50 +00:00
this . pm = ( PowerManager ) getSystemService ( Context . POWER_SERVICE ) ;
2018-10-04 14:46:35 +00:00
this . wakeLock = pm . newWakeLock ( PowerManager . PARTIAL_WAKE_LOCK , " Conversations:Service " ) ;
2014-03-11 14:44:22 +00:00
2018-08-18 14:27:50 +00:00
toggleForegroundService ( ) ;
updateUnreadCountBadge ( ) ;
toggleScreenEventReceiver ( ) ;
2020-08-19 11:57:33 +00:00
final IntentFilter intentFilter = new IntentFilter ( ) ;
intentFilter . addAction ( TorServiceUtils . ACTION_STATUS ) ;
2018-08-18 14:27:50 +00:00
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . M ) {
scheduleNextIdlePing ( ) ;
2018-09-06 21:17:37 +00:00
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . N ) {
intentFilter . addAction ( ConnectivityManager . CONNECTIVITY_ACTION ) ;
}
intentFilter . addAction ( NotificationManager . ACTION_INTERRUPTION_FILTER_CHANGED ) ;
2018-08-18 14:27:50 +00:00
}
2020-08-19 11:57:33 +00:00
registerReceiver ( this . mInternalEventReceiver , intentFilter ) ;
2019-01-13 10:27:13 +00:00
mForceDuringOnCreate . set ( false ) ;
toggleForegroundService ( ) ;
2020-08-24 07:51:26 +00:00
setupPhoneStateListener ( ) ;
}
private void setupPhoneStateListener ( ) {
final TelephonyManager telephonyManager = ( TelephonyManager ) getSystemService ( Context . TELEPHONY_SERVICE ) ;
if ( telephonyManager ! = null ) {
telephonyManager . listen ( phoneStateListener , PhoneStateListener . LISTEN_CALL_STATE ) ;
}
}
public boolean isPhoneInCall ( ) {
return isPhoneInCall . get ( ) ;
2018-08-18 14:27:50 +00:00
}
2019-01-10 13:52:27 +00:00
private void checkForDeletedFiles ( ) {
2019-01-12 09:21:21 +00:00
if ( destroyed ) {
Log . d ( Config . LOGTAG , " Do not check for deleted files because service has been destroyed " ) ;
return ;
}
2019-01-24 14:03:58 +00:00
final long start = SystemClock . elapsedRealtime ( ) ;
final List < DatabaseBackend . FilePathInfo > relativeFilePaths = databaseBackend . getFilePathInfo ( ) ;
final List < DatabaseBackend . FilePathInfo > changed = new ArrayList < > ( ) ;
2019-10-07 07:51:03 +00:00
for ( final DatabaseBackend . FilePathInfo filePath : relativeFilePaths ) {
2019-01-12 09:21:21 +00:00
if ( destroyed ) {
Log . d ( Config . LOGTAG , " Stop checking for deleted files because service has been destroyed " ) ;
return ;
}
2019-01-10 13:52:27 +00:00
final File file = fileBackend . getFileForPath ( filePath . path ) ;
2019-01-24 14:03:58 +00:00
if ( filePath . setDeleted ( ! file . exists ( ) ) ) {
changed . add ( filePath ) ;
2019-01-10 13:52:27 +00:00
}
}
2019-01-24 14:03:58 +00:00
final long duration = SystemClock . elapsedRealtime ( ) - start ;
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , " found " + changed . size ( ) + " changed files on start up. total= " + relativeFilePaths . size ( ) + " . ( " + duration + " ms) " ) ;
2019-01-24 14:03:58 +00:00
if ( changed . size ( ) > 0 ) {
databaseBackend . markFilesAsChanged ( changed ) ;
markChangedFiles ( changed ) ;
2019-01-10 13:52:27 +00:00
}
}
2018-09-06 13:55:54 +00:00
public void startContactObserver ( ) {
getContentResolver ( ) . registerContentObserver ( ContactsContract . Contacts . CONTENT_URI , true , new ContentObserver ( null ) {
@Override
public void onChange ( boolean selfChange ) {
super . onChange ( selfChange ) ;
if ( restoredFromDatabaseLatch . getCount ( ) = = 0 ) {
loadPhoneContacts ( ) ;
}
}
} ) ;
}
2018-08-18 14:27:50 +00:00
@Override
public void onTrimMemory ( int level ) {
super . onTrimMemory ( level ) ;
if ( level > = TRIM_MEMORY_COMPLETE ) {
Log . d ( Config . LOGTAG , " clear cache due to low memory " ) ;
getBitmapCache ( ) . evictAll ( ) ;
}
}
@Override
public void onDestroy ( ) {
try {
2018-09-06 21:17:37 +00:00
unregisterReceiver ( this . mInternalEventReceiver ) ;
2020-08-19 11:57:33 +00:00
} catch ( final IllegalArgumentException e ) {
2018-08-18 14:27:50 +00:00
//ignored
}
2019-01-13 08:42:44 +00:00
destroyed = false ;
2018-08-18 14:27:50 +00:00
fileObserver . stopWatching ( ) ;
super . onDestroy ( ) ;
}
public void restartFileObserver ( ) {
Log . d ( Config . LOGTAG , " restarting file observer " ) ;
2019-01-12 09:21:21 +00:00
mFileAddingExecutor . execute ( this . fileObserver : : restartWatching ) ;
2019-01-10 13:52:27 +00:00
mFileAddingExecutor . execute ( this : : checkForDeletedFiles ) ;
2018-08-18 14:27:50 +00:00
}
public void toggleScreenEventReceiver ( ) {
if ( awayWhenScreenOff ( ) & & ! manuallyChangePresence ( ) ) {
2018-09-06 21:17:37 +00:00
final IntentFilter filter = new IntentFilter ( ) ;
filter . addAction ( Intent . ACTION_SCREEN_ON ) ;
2018-08-18 14:27:50 +00:00
filter . addAction ( Intent . ACTION_SCREEN_OFF ) ;
2018-09-06 21:17:37 +00:00
registerReceiver ( this . mInternalScreenEventReceiver , filter ) ;
2018-08-18 14:27:50 +00:00
} else {
try {
2018-09-06 21:17:37 +00:00
unregisterReceiver ( this . mInternalScreenEventReceiver ) ;
2018-08-18 14:27:50 +00:00
} catch ( IllegalArgumentException e ) {
//ignored
}
}
}
public void toggleForegroundService ( ) {
2018-11-17 11:57:36 +00:00
toggleForegroundService ( false ) ;
}
2020-04-15 20:40:37 +00:00
public void setOngoingCall ( AbstractJingleConnection . Id id , Set < Media > media ) {
ongoingCall . set ( new OngoingCall ( id , media ) ) ;
2020-04-11 17:05:07 +00:00
toggleForegroundService ( false ) ;
}
2020-04-15 20:40:37 +00:00
public void removeOngoingCall ( ) {
ongoingCall . set ( null ) ;
toggleForegroundService ( false ) ;
2020-04-11 17:05:07 +00:00
}
2018-11-17 11:57:36 +00:00
private void toggleForegroundService ( boolean force ) {
2018-09-13 16:47:57 +00:00
final boolean status ;
2020-04-15 20:40:37 +00:00
final OngoingCall ongoing = ongoingCall . get ( ) ;
2020-04-11 17:05:07 +00:00
if ( force | | mForceDuringOnCreate . get ( ) | | mForceForegroundService . get ( ) | | ongoing ! = null | | ( Compatibility . keepForegroundService ( this ) & & hasEnabledAccounts ( ) ) ) {
final Notification notification ;
if ( ongoing ! = null ) {
2020-04-15 20:40:37 +00:00
notification = this . mNotificationService . getOngoingCallNotification ( ongoing . id , ongoing . media ) ;
2020-04-11 17:05:07 +00:00
startForeground ( NotificationService . ONGOING_CALL_NOTIFICATION_ID , notification ) ;
mNotificationService . cancel ( NotificationService . FOREGROUND_NOTIFICATION_ID ) ;
} else {
notification = this . mNotificationService . createForegroundNotification ( ) ;
startForeground ( NotificationService . FOREGROUND_NOTIFICATION_ID , notification ) ;
}
2019-02-16 10:58:16 +00:00
if ( ! mForceForegroundService . get ( ) ) {
mNotificationService . notify ( NotificationService . FOREGROUND_NOTIFICATION_ID , notification ) ;
}
2018-09-13 16:47:57 +00:00
status = true ;
2018-08-18 14:27:50 +00:00
} else {
stopForeground ( true ) ;
2018-09-13 16:47:57 +00:00
status = false ;
2018-08-18 14:27:50 +00:00
}
2019-02-16 10:58:16 +00:00
if ( ! mForceForegroundService . get ( ) ) {
2020-04-11 17:05:07 +00:00
mNotificationService . cancel ( NotificationService . FOREGROUND_NOTIFICATION_ID ) ;
}
if ( ongoing = = null ) {
mNotificationService . cancel ( NotificationService . ONGOING_CALL_NOTIFICATION_ID ) ;
2019-02-16 10:58:16 +00:00
}
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , " ForegroundService: " + ( status ? " on " : " off " ) ) ;
2018-08-18 14:27:50 +00:00
}
2018-11-11 08:54:52 +00:00
public boolean foregroundNotificationNeedsUpdatingWhenErrorStateChanges ( ) {
2020-04-11 17:47:30 +00:00
return ! mForceForegroundService . get ( ) & & ongoingCall . get ( ) = = null & & Compatibility . keepForegroundService ( this ) & & hasEnabledAccounts ( ) ;
2018-11-11 08:54:52 +00:00
}
2018-08-18 14:27:50 +00:00
@Override
public void onTaskRemoved ( final Intent rootIntent ) {
super . onTaskRemoved ( rootIntent ) ;
2020-04-11 17:05:07 +00:00
if ( ( Compatibility . keepForegroundService ( this ) & & hasEnabledAccounts ( ) ) | | mForceForegroundService . get ( ) | | ongoingCall . get ( ) ! = null ) {
2018-08-18 14:27:50 +00:00
Log . d ( Config . LOGTAG , " ignoring onTaskRemoved because foreground service is activated " ) ;
} else {
this . logoutAndSave ( false ) ;
}
}
private void logoutAndSave ( boolean stop ) {
int activeAccounts = 0 ;
for ( final Account account : accounts ) {
if ( account . getStatus ( ) ! = Account . State . DISABLED ) {
databaseBackend . writeRoster ( account . getRoster ( ) ) ;
activeAccounts + + ;
}
if ( account . getXmppConnection ( ) ! = null ) {
new Thread ( ( ) - > disconnect ( account , false ) ) . start ( ) ;
}
}
if ( stop | | activeAccounts = = 0 ) {
Log . d ( Config . LOGTAG , " good bye " ) ;
stopSelf ( ) ;
}
}
2014-02-13 22:40:08 +00:00
2019-01-27 19:54:45 +00:00
private void schedulePostConnectivityChange ( ) {
final AlarmManager alarmManager = ( AlarmManager ) getSystemService ( Context . ALARM_SERVICE ) ;
if ( alarmManager = = null ) {
return ;
}
final long triggerAtMillis = SystemClock . elapsedRealtime ( ) + ( Config . POST_CONNECTIVITY_CHANGE_PING_INTERVAL * 1000 ) ;
final Intent intent = new Intent ( this , EventReceiver . class ) ;
intent . setAction ( ACTION_POST_CONNECTIVITY_CHANGE ) ;
try {
final PendingIntent pendingIntent = PendingIntent . getBroadcast ( this , 1 , intent , 0 ) ;
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . M ) {
alarmManager . setAndAllowWhileIdle ( AlarmManager . ELAPSED_REALTIME_WAKEUP , triggerAtMillis , pendingIntent ) ;
} else {
alarmManager . set ( AlarmManager . ELAPSED_REALTIME_WAKEUP , triggerAtMillis , pendingIntent ) ;
}
} catch ( RuntimeException e ) {
Log . e ( Config . LOGTAG , " unable to schedule alarm for post connectivity change " , e ) ;
}
}
2018-08-18 14:27:50 +00:00
public void scheduleWakeUpCall ( int seconds , int requestCode ) {
final long timeToWake = SystemClock . elapsedRealtime ( ) + ( seconds < 0 ? 1 : seconds + 1 ) * 1000 ;
final AlarmManager alarmManager = ( AlarmManager ) getSystemService ( Context . ALARM_SERVICE ) ;
if ( alarmManager = = null ) {
return ;
2018-07-16 18:42:17 +00:00
}
2018-08-18 14:27:50 +00:00
final Intent intent = new Intent ( this , EventReceiver . class ) ;
intent . setAction ( " ping " ) ;
try {
PendingIntent pendingIntent = PendingIntent . getBroadcast ( this , requestCode , intent , 0 ) ;
alarmManager . set ( AlarmManager . ELAPSED_REALTIME_WAKEUP , timeToWake , pendingIntent ) ;
} catch ( RuntimeException e ) {
Log . e ( Config . LOGTAG , " unable to schedule alarm for ping " , e ) ;
}
}
@TargetApi ( Build . VERSION_CODES . M )
private void scheduleNextIdlePing ( ) {
final long timeToWake = SystemClock . elapsedRealtime ( ) + ( Config . IDLE_PING_INTERVAL * 1000 ) ;
final AlarmManager alarmManager = ( AlarmManager ) getSystemService ( Context . ALARM_SERVICE ) ;
if ( alarmManager = = null ) {
return ;
}
final Intent intent = new Intent ( this , EventReceiver . class ) ;
intent . setAction ( ACTION_IDLE_PING ) ;
try {
2018-07-16 18:42:17 +00:00
PendingIntent pendingIntent = PendingIntent . getBroadcast ( this , 0 , intent , 0 ) ;
2018-08-18 14:27:50 +00:00
alarmManager . setAndAllowWhileIdle ( AlarmManager . ELAPSED_REALTIME_WAKEUP , timeToWake , pendingIntent ) ;
} catch ( RuntimeException e ) {
Log . d ( Config . LOGTAG , " unable to schedule alarm for idle ping " , e ) ;
}
}
public XmppConnection createConnection ( final Account account ) {
final XmppConnection connection = new XmppConnection ( account , this ) ;
connection . setOnMessagePacketReceivedListener ( this . mMessageParser ) ;
connection . setOnStatusChangedListener ( this . statusListener ) ;
connection . setOnPresencePacketReceivedListener ( this . mPresenceParser ) ;
connection . setOnUnregisteredIqPacketReceivedListener ( this . mIqParser ) ;
2020-04-01 11:25:52 +00:00
connection . setOnJinglePacketReceivedListener ( ( ( a , jp ) - > mJingleConnectionManager . deliverPacket ( a , jp ) ) ) ;
2018-08-18 14:27:50 +00:00
connection . setOnBindListener ( this . mOnBindListener ) ;
connection . setOnMessageAcknowledgeListener ( this . mOnMessageAcknowledgedListener ) ;
connection . addOnAdvancedStreamFeaturesAvailableListener ( this . mMessageArchiveService ) ;
connection . addOnAdvancedStreamFeaturesAvailableListener ( this . mAvatarService ) ;
AxolotlService axolotlService = account . getAxolotlService ( ) ;
if ( axolotlService ! = null ) {
connection . addOnAdvancedStreamFeaturesAvailableListener ( axolotlService ) ;
}
return connection ;
}
2014-02-11 22:55:03 +00:00
2018-08-18 14:27:50 +00:00
public void sendChatState ( Conversation conversation ) {
if ( sendChatStates ( ) ) {
MessagePacket packet = mMessageGenerator . generateChatState ( conversation ) ;
sendMessagePacket ( conversation . getAccount ( ) , packet ) ;
}
}
private void sendFileMessage ( final Message message , final boolean delay ) {
Log . d ( Config . LOGTAG , " send file message " ) ;
final Account account = message . getConversation ( ) . getAccount ( ) ;
if ( account . httpUploadAvailable ( fileBackend . getFile ( message , false ) . getSize ( ) )
| | message . getConversation ( ) . getMode ( ) = = Conversation . MODE_MULTI ) {
mHttpConnectionManager . createNewUploadConnection ( message , delay ) ;
} else {
2020-04-01 08:45:03 +00:00
mJingleConnectionManager . startJingleFileTransfer ( message ) ;
2018-08-18 14:27:50 +00:00
}
}
2014-05-18 09:25:04 +00:00
2018-08-18 14:27:50 +00:00
public void sendMessage ( final Message message ) {
sendMessage ( message , false , false ) ;
}
2014-02-11 22:55:03 +00:00
2018-08-18 14:27:50 +00:00
private void sendMessage ( final Message message , final boolean resend , final boolean delay ) {
final Account account = message . getConversation ( ) . getAccount ( ) ;
if ( account . setShowErrorNotification ( true ) ) {
databaseBackend . updateAccount ( account ) ;
mNotificationService . updateErrorNotification ( ) ;
}
final Conversation conversation = ( Conversation ) message . getConversation ( ) ;
account . deactivateGracePeriod ( ) ;
2018-11-08 11:20:46 +00:00
if ( QuickConversationsService . isQuicksy ( ) & & conversation . getMode ( ) = = Conversation . MODE_SINGLE ) {
final Contact contact = conversation . getContact ( ) ;
if ( ! contact . showInRoster ( ) & & contact . getOption ( Contact . Options . SYNCED_VIA_OTHER ) ) {
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : adding " + contact . getJid ( ) + " on sending message " ) ;
2018-11-08 11:20:46 +00:00
createContact ( contact , true ) ;
}
}
2018-08-18 14:27:50 +00:00
MessagePacket packet = null ;
final boolean addToConversation = ( conversation . getMode ( ) ! = Conversation . MODE_MULTI
| | ! Patches . BAD_MUC_REFLECTION . contains ( account . getServerIdentity ( ) ) )
& & ! message . edited ( ) ;
boolean saveInDb = addToConversation ;
message . setStatus ( Message . STATUS_WAITING ) ;
2019-02-07 12:18:42 +00:00
if ( message . getEncryption ( ) ! = Message . ENCRYPTION_NONE & & conversation . getMode ( ) = = Conversation . MODE_MULTI & & conversation . isPrivateAndNonAnonymous ( ) ) {
if ( conversation . setAttribute ( Conversation . ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS , true ) ) {
databaseBackend . updateConversation ( conversation ) ;
}
}
2019-09-19 08:00:50 +00:00
final boolean inProgressJoin = isJoinInProgress ( conversation ) ;
2019-06-18 16:09:44 +00:00
if ( account . isOnlineAndConnected ( ) & & ! inProgressJoin ) {
2018-08-18 14:27:50 +00:00
switch ( message . getEncryption ( ) ) {
case Message . ENCRYPTION_NONE :
if ( message . needsUploading ( ) ) {
if ( account . httpUploadAvailable ( fileBackend . getFile ( message , false ) . getSize ( ) )
| | conversation . getMode ( ) = = Conversation . MODE_MULTI
| | message . fixCounterpart ( ) ) {
this . sendFileMessage ( message , delay ) ;
} else {
break ;
}
} else {
packet = mMessageGenerator . generateChat ( message ) ;
}
break ;
case Message . ENCRYPTION_PGP :
case Message . ENCRYPTION_DECRYPTED :
if ( message . needsUploading ( ) ) {
if ( account . httpUploadAvailable ( fileBackend . getFile ( message , false ) . getSize ( ) )
| | conversation . getMode ( ) = = Conversation . MODE_MULTI
| | message . fixCounterpart ( ) ) {
this . sendFileMessage ( message , delay ) ;
} else {
break ;
}
} else {
packet = mMessageGenerator . generatePgpChat ( message ) ;
}
break ;
case Message . ENCRYPTION_AXOLOTL :
message . setFingerprint ( account . getAxolotlService ( ) . getOwnFingerprint ( ) ) ;
if ( message . needsUploading ( ) ) {
if ( account . httpUploadAvailable ( fileBackend . getFile ( message , false ) . getSize ( ) )
| | conversation . getMode ( ) = = Conversation . MODE_MULTI
| | message . fixCounterpart ( ) ) {
this . sendFileMessage ( message , delay ) ;
} else {
break ;
}
} else {
XmppAxolotlMessage axolotlMessage = account . getAxolotlService ( ) . fetchAxolotlMessageFromCache ( message ) ;
if ( axolotlMessage = = null ) {
account . getAxolotlService ( ) . preparePayloadMessage ( message , delay ) ;
} else {
packet = mMessageGenerator . generateAxolotlChat ( message , axolotlMessage ) ;
}
}
break ;
}
if ( packet ! = null ) {
if ( account . getXmppConnection ( ) . getFeatures ( ) . sm ( )
| | ( conversation . getMode ( ) = = Conversation . MODE_MULTI & & message . getCounterpart ( ) . isBareJid ( ) ) ) {
message . setStatus ( Message . STATUS_UNSEND ) ;
} else {
message . setStatus ( Message . STATUS_SEND ) ;
}
}
} else {
switch ( message . getEncryption ( ) ) {
case Message . ENCRYPTION_DECRYPTED :
if ( ! message . needsUploading ( ) ) {
String pgpBody = message . getEncryptedBody ( ) ;
String decryptedBody = message . getBody ( ) ;
message . setBody ( pgpBody ) ; //TODO might throw NPE
message . setEncryption ( Message . ENCRYPTION_PGP ) ;
if ( message . edited ( ) ) {
message . setBody ( decryptedBody ) ;
message . setEncryption ( Message . ENCRYPTION_DECRYPTED ) ;
2018-12-01 14:52:44 +00:00
if ( ! databaseBackend . updateMessage ( message , message . getEditedId ( ) ) ) {
2019-10-07 07:51:03 +00:00
Log . e ( Config . LOGTAG , " error updated message in DB after edit " ) ;
2018-12-01 14:52:44 +00:00
}
2018-08-18 14:27:50 +00:00
updateConversationUi ( ) ;
return ;
} else {
databaseBackend . createMessage ( message ) ;
saveInDb = false ;
message . setBody ( decryptedBody ) ;
message . setEncryption ( Message . ENCRYPTION_DECRYPTED ) ;
}
}
break ;
case Message . ENCRYPTION_AXOLOTL :
message . setFingerprint ( account . getAxolotlService ( ) . getOwnFingerprint ( ) ) ;
break ;
}
}
2014-08-26 14:52:42 +00:00
2019-04-27 09:46:43 +00:00
boolean mucMessage = conversation . getMode ( ) = = Conversation . MODE_MULTI & & ! message . isPrivateMessage ( ) ;
2018-08-18 14:27:50 +00:00
if ( mucMessage ) {
message . setCounterpart ( conversation . getMucOptions ( ) . getSelf ( ) . getFullJid ( ) ) ;
}
2014-08-26 14:52:42 +00:00
2018-08-18 14:27:50 +00:00
if ( resend ) {
if ( packet ! = null & & addToConversation ) {
if ( account . getXmppConnection ( ) . getFeatures ( ) . sm ( ) | | mucMessage ) {
markMessage ( message , Message . STATUS_UNSEND ) ;
} else {
markMessage ( message , Message . STATUS_SEND ) ;
}
}
} else {
if ( addToConversation ) {
conversation . add ( message ) ;
}
if ( saveInDb ) {
databaseBackend . createMessage ( message ) ;
} else if ( message . edited ( ) ) {
2018-12-01 14:52:44 +00:00
if ( ! databaseBackend . updateMessage ( message , message . getEditedId ( ) ) ) {
2019-10-07 07:51:03 +00:00
Log . e ( Config . LOGTAG , " error updated message in DB after edit " ) ;
2018-12-01 14:52:44 +00:00
}
2018-08-18 14:27:50 +00:00
}
updateConversationUi ( ) ;
}
if ( packet ! = null ) {
if ( delay ) {
mMessageGenerator . addDelay ( packet , message . getTimeSent ( ) ) ;
}
2020-01-18 11:08:03 +00:00
if ( conversation . setOutgoingChatState ( Config . DEFAULT_CHAT_STATE ) ) {
2018-08-18 14:27:50 +00:00
if ( this . sendChatStates ( ) ) {
packet . addChild ( ChatState . toElement ( conversation . getOutgoingChatState ( ) ) ) ;
}
}
sendMessagePacket ( account , packet ) ;
}
}
2019-09-19 08:00:50 +00:00
private boolean isJoinInProgress ( final Conversation conversation ) {
final Account account = conversation . getAccount ( ) ;
synchronized ( account . inProgressConferenceJoins ) {
if ( conversation . getMode ( ) = = Conversational . MODE_MULTI ) {
final boolean inProgress = account . inProgressConferenceJoins . contains ( conversation ) ;
final boolean pending = account . pendingConferenceJoins . contains ( conversation ) ;
final boolean inProgressJoin = inProgress | | pending ;
if ( inProgressJoin ) {
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : holding back message to group. inProgress= " + inProgress + " , pending= " + pending ) ;
2019-09-19 08:00:50 +00:00
}
return inProgressJoin ;
} else {
return false ;
}
}
}
2018-08-18 14:27:50 +00:00
private void sendUnsentMessages ( final Conversation conversation ) {
conversation . findWaitingMessages ( message - > resendMessage ( message , true ) ) ;
}
public void resendMessage ( final Message message , final boolean delay ) {
sendMessage ( message , true , delay ) ;
}
public void fetchRosterFromServer ( final Account account ) {
final IqPacket iqPacket = new IqPacket ( IqPacket . TYPE . GET ) ;
if ( ! " " . equals ( account . getRosterVersion ( ) ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( )
+ " : fetching roster version " + account . getRosterVersion ( ) ) ;
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : fetching roster " ) ;
}
iqPacket . query ( Namespace . ROSTER ) . setAttribute ( " ver " , account . getRosterVersion ( ) ) ;
sendIqPacket ( account , iqPacket , mIqParser ) ;
}
public void fetchBookmarks ( final Account account ) {
final IqPacket iqPacket = new IqPacket ( IqPacket . TYPE . GET ) ;
final Element query = iqPacket . query ( " jabber:iq:private " ) ;
query . addChild ( " storage " , Namespace . BOOKMARKS ) ;
final OnIqPacketReceived callback = ( a , response ) - > {
if ( response . getType ( ) = = IqPacket . TYPE . RESULT ) {
final Element query1 = response . query ( ) ;
final Element storage = query1 . findChild ( " storage " , " storage:bookmarks " ) ;
2019-09-28 00:23:15 +00:00
Map < Jid , Bookmark > bookmarks = Bookmark . parseFromStorage ( storage , account ) ;
2019-09-27 22:32:29 +00:00
processBookmarksInitial ( a , bookmarks , false ) ;
2018-08-18 14:27:50 +00:00
} else {
Log . d ( Config . LOGTAG , a . getJid ( ) . asBareJid ( ) + " : could not fetch bookmarks " ) ;
}
} ;
sendIqPacket ( account , iqPacket , callback ) ;
}
2019-09-27 22:32:29 +00:00
public void fetchBookmarks2 ( final Account account ) {
final IqPacket retrieve = mIqGenerator . retrieveBookmarks ( ) ;
sendIqPacket ( account , retrieve , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( final Account account , final IqPacket response ) {
if ( response . getType ( ) = = IqPacket . TYPE . RESULT ) {
final Element pubsub = response . findChild ( " pubsub " , Namespace . PUBSUB ) ;
2019-09-28 00:23:15 +00:00
final Map < Jid , Bookmark > bookmarks = Bookmark . parseFromPubsub ( pubsub , account ) ;
2019-09-27 22:32:29 +00:00
processBookmarksInitial ( account , bookmarks , true ) ;
}
}
} ) ;
}
2019-10-07 07:51:03 +00:00
public void processBookmarksInitial ( Account account , Map < Jid , Bookmark > bookmarks , final boolean pep ) {
2019-01-03 13:07:03 +00:00
final Set < Jid > previousBookmarks = account . getBookmarkedJids ( ) ;
2018-12-15 16:44:23 +00:00
final boolean synchronizeWithBookmarks = synchronizeWithBookmarks ( ) ;
2019-09-28 00:23:15 +00:00
for ( Bookmark bookmark : bookmarks . values ( ) ) {
2019-09-27 22:32:29 +00:00
previousBookmarks . remove ( bookmark . getJid ( ) . asBareJid ( ) ) ;
2019-09-28 08:16:29 +00:00
processModifiedBookmark ( bookmark , pep , synchronizeWithBookmarks ) ;
2019-09-27 22:32:29 +00:00
}
if ( pep & & synchronizeWithBookmarks ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : " + previousBookmarks . size ( ) + " bookmarks have been removed " ) ;
for ( Jid jid : previousBookmarks ) {
2019-09-28 08:16:29 +00:00
processDeletedBookmark ( account , jid ) ;
2019-01-03 13:07:03 +00:00
}
2018-08-18 14:27:50 +00:00
}
2019-09-28 00:23:15 +00:00
account . setBookmarks ( bookmarks ) ;
2018-08-18 14:27:50 +00:00
}
2019-09-28 08:16:29 +00:00
public void processDeletedBookmark ( Account account , Jid jid ) {
final Conversation conversation = find ( account , jid ) ;
if ( conversation ! = null & & conversation . getMucOptions ( ) . getError ( ) = = MucOptions . Error . DESTROYED ) {
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : archiving destroyed conference ( " + conversation . getJid ( ) + " ) after receiving pep " ) ;
2019-09-28 08:16:29 +00:00
archiveConversation ( conversation , false ) ;
}
}
private void processModifiedBookmark ( Bookmark bookmark , final boolean pep , final boolean synchronizeWithBookmarks ) {
final Account account = bookmark . getAccount ( ) ;
Conversation conversation = find ( bookmark ) ;
if ( conversation ! = null ) {
if ( conversation . getMode ( ) ! = Conversation . MODE_MULTI ) {
return ;
}
bookmark . setConversation ( conversation ) ;
if ( pep & & synchronizeWithBookmarks & & ! bookmark . autojoin ( ) ) {
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : archiving conference ( " + conversation . getJid ( ) + " ) after receiving pep " ) ;
2019-09-28 08:16:29 +00:00
archiveConversation ( conversation , false ) ;
2019-09-28 19:46:27 +00:00
} else {
final MucOptions mucOptions = conversation . getMucOptions ( ) ;
if ( mucOptions . getError ( ) = = MucOptions . Error . NICK_IN_USE ) {
final String current = mucOptions . getActualNick ( ) ;
final String proposed = mucOptions . getProposedNick ( ) ;
if ( current ! = null & & ! current . equals ( proposed ) ) {
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : proposed nick changed after bookmark push " + current + " -> " + proposed ) ;
2019-09-28 19:46:27 +00:00
joinMuc ( conversation ) ;
}
}
2019-09-28 08:16:29 +00:00
}
} else if ( synchronizeWithBookmarks & & bookmark . autojoin ( ) ) {
conversation = findOrCreateConversation ( account , bookmark . getFullJid ( ) , true , true , false ) ;
bookmark . setConversation ( conversation ) ;
}
}
public void processModifiedBookmark ( Bookmark bookmark ) {
final boolean synchronizeWithBookmarks = synchronizeWithBookmarks ( ) ;
processModifiedBookmark ( bookmark , true , synchronizeWithBookmarks ) ;
}
2019-09-27 22:32:29 +00:00
public void createBookmark ( final Account account , final Bookmark bookmark ) {
2019-09-28 00:23:15 +00:00
account . putBookmark ( bookmark ) ;
2019-09-27 23:21:19 +00:00
final XmppConnection connection = account . getXmppConnection ( ) ;
if ( connection . getFeatures ( ) . bookmarks2 ( ) ) {
final Element item = mIqGenerator . publishBookmarkItem ( bookmark ) ;
2019-09-28 11:14:59 +00:00
pushNodeAndEnforcePublishOptions ( account , Namespace . BOOKMARKS2 , item , bookmark . getJid ( ) . asBareJid ( ) . toEscapedString ( ) , PublishOptions . persistentWhitelistAccessMaxItems ( ) ) ;
2019-09-27 23:21:19 +00:00
} else if ( connection . getFeatures ( ) . bookmarksConversion ( ) ) {
pushBookmarksPep ( account ) ;
} else {
pushBookmarksPrivateXml ( account ) ;
}
2019-09-27 22:32:29 +00:00
}
public void deleteBookmark ( final Account account , final Bookmark bookmark ) {
2019-09-28 00:23:15 +00:00
account . removeBookmark ( bookmark ) ;
2019-06-16 16:05:06 +00:00
final XmppConnection connection = account . getXmppConnection ( ) ;
2019-10-07 11:49:39 +00:00
if ( connection . getFeatures ( ) . bookmarks2 ( ) ) {
2019-09-28 11:14:59 +00:00
IqPacket request = mIqGenerator . deleteItem ( Namespace . BOOKMARKS2 , bookmark . getJid ( ) . asBareJid ( ) . toEscapedString ( ) ) ;
2019-10-07 07:51:03 +00:00
sendIqPacket ( account , request , ( a , response ) - > {
if ( response . getType ( ) = = IqPacket . TYPE . ERROR ) {
Log . d ( Config . LOGTAG , a . getJid ( ) . asBareJid ( ) + " : unable to delete bookmark " + response . getError ( ) ) ;
2019-09-27 23:21:19 +00:00
}
} ) ;
} else if ( connection . getFeatures ( ) . bookmarksConversion ( ) ) {
pushBookmarksPep ( account ) ;
} else {
pushBookmarksPrivateXml ( account ) ;
}
2018-08-18 14:27:50 +00:00
}
private void pushBookmarksPrivateXml ( Account account ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : pushing bookmarks via private xml " ) ;
IqPacket iqPacket = new IqPacket ( IqPacket . TYPE . SET ) ;
Element query = iqPacket . query ( " jabber:iq:private " ) ;
Element storage = query . addChild ( " storage " , " storage:bookmarks " ) ;
for ( Bookmark bookmark : account . getBookmarks ( ) ) {
storage . addChild ( bookmark ) ;
}
sendIqPacket ( account , iqPacket , mDefaultIqHandler ) ;
}
private void pushBookmarksPep ( Account account ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : pushing bookmarks via pep " ) ;
Element storage = new Element ( " storage " , " storage:bookmarks " ) ;
for ( Bookmark bookmark : account . getBookmarks ( ) ) {
storage . addChild ( bookmark ) ;
}
2020-05-19 17:36:47 +00:00
pushNodeAndEnforcePublishOptions ( account , Namespace . BOOKMARKS , storage , " current " , PublishOptions . persistentWhitelistAccess ( ) ) ;
2019-09-27 22:32:29 +00:00
}
private void pushNodeAndEnforcePublishOptions ( final Account account , final String node , final Element element , final String id , final Bundle options ) {
pushNodeAndEnforcePublishOptions ( account , node , element , id , options , true ) ;
2018-08-18 14:27:50 +00:00
}
2019-10-07 07:51:03 +00:00
private void pushNodeAndEnforcePublishOptions ( final Account account , final String node , final Element element , final String id , final Bundle options , final boolean retry ) {
2019-09-27 22:32:29 +00:00
final IqPacket packet = mIqGenerator . publishElement ( node , element , id , options ) ;
2018-08-18 14:27:50 +00:00
sendIqPacket ( account , packet , ( a , response ) - > {
if ( response . getType ( ) = = IqPacket . TYPE . RESULT ) {
return ;
}
2018-11-25 19:58:48 +00:00
if ( retry & & PublishOptions . preconditionNotMet ( response ) ) {
2018-08-18 14:27:50 +00:00
pushNodeConfiguration ( account , node , options , new OnConfigurationPushed ( ) {
@Override
public void onPushSucceeded ( ) {
2019-09-27 22:32:29 +00:00
pushNodeAndEnforcePublishOptions ( account , node , element , id , options , false ) ;
2018-08-18 14:27:50 +00:00
}
@Override
public void onPushFailed ( ) {
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : unable to push node configuration ( " + node + " ) " ) ;
2018-08-18 14:27:50 +00:00
}
} ) ;
} else {
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : error publishing bookmarks (retry= " + Boolean . toString ( retry ) + " ) " + response ) ;
2018-08-18 14:27:50 +00:00
}
} ) ;
}
2014-02-10 02:34:00 +00:00
2019-10-07 07:51:03 +00:00
private void restoreFromDatabase ( ) {
synchronized ( this . conversations ) {
final Map < String , Account > accountLookupTable = new Hashtable < > ( ) ;
for ( Account account : this . accounts ) {
accountLookupTable . put ( account . getUuid ( ) , account ) ;
}
Log . d ( Config . LOGTAG , " restoring conversations... " ) ;
final long startTimeConversationsRestore = SystemClock . elapsedRealtime ( ) ;
this . conversations . addAll ( databaseBackend . getConversations ( Conversation . STATUS_AVAILABLE ) ) ;
for ( Iterator < Conversation > iterator = conversations . listIterator ( ) ; iterator . hasNext ( ) ; ) {
Conversation conversation = iterator . next ( ) ;
Account account = accountLookupTable . get ( conversation . getAccountUuid ( ) ) ;
if ( account ! = null ) {
conversation . setAccount ( account ) ;
} else {
Log . e ( Config . LOGTAG , " unable to restore Conversations with " + conversation . getJid ( ) ) ;
iterator . remove ( ) ;
}
}
long diffConversationsRestore = SystemClock . elapsedRealtime ( ) - startTimeConversationsRestore ;
Log . d ( Config . LOGTAG , " finished restoring conversations in " + diffConversationsRestore + " ms " ) ;
Runnable runnable = ( ) - > {
long deletionDate = getAutomaticMessageDeletionDate ( ) ;
mLastExpiryRun . set ( SystemClock . elapsedRealtime ( ) ) ;
if ( deletionDate > 0 ) {
Log . d ( Config . LOGTAG , " deleting messages that are older than " + AbstractGenerator . getTimestamp ( deletionDate ) ) ;
databaseBackend . expireOldMessages ( deletionDate ) ;
}
Log . d ( Config . LOGTAG , " restoring roster... " ) ;
for ( Account account : accounts ) {
databaseBackend . readRoster ( account . getRoster ( ) ) ;
account . initAccountServices ( XmppConnectionService . this ) ; //roster needs to be loaded at this stage
}
getBitmapCache ( ) . evictAll ( ) ;
loadPhoneContacts ( ) ;
Log . d ( Config . LOGTAG , " restoring messages... " ) ;
final long startMessageRestore = SystemClock . elapsedRealtime ( ) ;
final Conversation quickLoad = QuickLoader . get ( this . conversations ) ;
if ( quickLoad ! = null ) {
restoreMessages ( quickLoad ) ;
updateConversationUi ( ) ;
final long diffMessageRestore = SystemClock . elapsedRealtime ( ) - startMessageRestore ;
Log . d ( Config . LOGTAG , " quickly restored " + quickLoad . getName ( ) + " after " + diffMessageRestore + " ms " ) ;
}
for ( Conversation conversation : this . conversations ) {
if ( quickLoad ! = conversation ) {
restoreMessages ( conversation ) ;
}
}
mNotificationService . finishBacklog ( false ) ;
restoredFromDatabaseLatch . countDown ( ) ;
final long diffMessageRestore = SystemClock . elapsedRealtime ( ) - startMessageRestore ;
Log . d ( Config . LOGTAG , " finished restoring messages in " + diffMessageRestore + " ms " ) ;
updateConversationUi ( ) ;
} ;
mDatabaseReaderExecutor . execute ( runnable ) ; //will contain one write command (expiry) but that's fine
}
}
private void restoreMessages ( Conversation conversation ) {
conversation . addAll ( 0 , databaseBackend . getMessages ( conversation , Config . PAGE_SIZE ) ) ;
conversation . findUnsentTextMessages ( message - > markMessage ( message , Message . STATUS_WAITING ) ) ;
conversation . findUnreadMessages ( message - > mNotificationService . pushFromBacklog ( message ) ) ;
}
public void loadPhoneContacts ( ) {
2018-10-26 22:32:09 +00:00
mContactMergerExecutor . execute ( ( ) - > {
2018-10-28 11:34:17 +00:00
Map < Jid , JabberIdContact > contacts = JabberIdContact . load ( this ) ;
Log . d ( Config . LOGTAG , " start merging phone contacts with roster " ) ;
for ( Account account : accounts ) {
2018-10-28 18:05:16 +00:00
List < Contact > withSystemAccounts = account . getRoster ( ) . getWithSystemAccounts ( JabberIdContact . class ) ;
2018-10-28 11:34:17 +00:00
for ( JabberIdContact jidContact : contacts . values ( ) ) {
final Contact contact = account . getRoster ( ) . getContact ( jidContact . getJid ( ) ) ;
2018-10-28 18:05:16 +00:00
boolean needsCacheClean = contact . setPhoneContact ( jidContact ) ;
2018-10-28 11:34:17 +00:00
if ( needsCacheClean ) {
getAvatarService ( ) . clear ( contact ) ;
2018-10-26 22:32:09 +00:00
}
2018-10-28 11:34:17 +00:00
withSystemAccounts . remove ( contact ) ;
}
for ( Contact contact : withSystemAccounts ) {
2018-10-28 18:05:16 +00:00
boolean needsCacheClean = contact . unsetPhoneContact ( JabberIdContact . class ) ;
2018-10-28 11:34:17 +00:00
if ( needsCacheClean ) {
getAvatarService ( ) . clear ( contact ) ;
2018-10-26 22:32:09 +00:00
}
}
2018-10-28 11:34:17 +00:00
}
Log . d ( Config . LOGTAG , " finished merging phone contacts " ) ;
mShortcutService . refresh ( mInitialAddressbookSyncCompleted . compareAndSet ( false , true ) ) ;
updateRosterUi ( ) ;
2018-10-26 22:32:09 +00:00
mQuickConversationsService . considerSync ( ) ;
} ) ;
2019-10-07 07:51:03 +00:00
}
2015-12-06 23:33:50 +00:00
2018-03-18 11:24:28 +00:00
2019-10-07 07:51:03 +00:00
public void syncRoster ( final Account account ) {
mRosterSyncTaskManager . execute ( account , ( ) - > databaseBackend . writeRoster ( account . getRoster ( ) ) ) ;
}
2018-03-18 11:24:28 +00:00
2019-10-07 07:51:03 +00:00
public List < Conversation > getConversations ( ) {
return this . conversations ;
}
2014-10-20 19:08:33 +00:00
2020-07-30 10:55:19 +00:00
private void markFileDeleted ( final File file ) {
2019-10-11 13:37:41 +00:00
synchronized ( FILENAMES_TO_IGNORE_DELETION ) {
2020-07-30 10:55:19 +00:00
if ( FILENAMES_TO_IGNORE_DELETION . remove ( file . getAbsolutePath ( ) ) ) {
Log . d ( Config . LOGTAG , " ignored deletion of " + file . getAbsolutePath ( ) ) ;
2019-10-11 13:37:41 +00:00
return ;
}
}
2019-01-10 13:52:27 +00:00
final boolean isInternalFile = fileBackend . isInternalFile ( file ) ;
final List < String > uuids = databaseBackend . markFileAsDeleted ( file , isInternalFile ) ;
2020-07-30 10:55:19 +00:00
Log . d ( Config . LOGTAG , " deleted file " + file . getAbsolutePath ( ) + " internal= " + isInternalFile + " , database hits= " + uuids . size ( ) ) ;
2019-01-10 20:24:24 +00:00
markUuidsAsDeletedFiles ( uuids ) ;
2019-10-07 07:51:03 +00:00
}
2014-09-05 11:29:20 +00:00
2019-10-07 07:51:03 +00:00
private void markUuidsAsDeletedFiles ( List < String > uuids ) {
2019-01-10 13:52:27 +00:00
boolean deleted = false ;
for ( Conversation conversation : getConversations ( ) ) {
deleted | = conversation . markAsDeleted ( uuids ) ;
}
2020-03-02 10:11:23 +00:00
for ( final String uuid : uuids ) {
2020-01-20 09:54:55 +00:00
evictPreview ( uuid ) ;
}
2019-01-10 13:52:27 +00:00
if ( deleted ) {
updateConversationUi ( ) ;
}
}
2019-01-24 14:03:58 +00:00
private void markChangedFiles ( List < DatabaseBackend . FilePathInfo > infos ) {
boolean changed = false ;
for ( Conversation conversation : getConversations ( ) ) {
changed | = conversation . markAsChanged ( infos ) ;
}
if ( changed ) {
updateConversationUi ( ) ;
}
}
2019-10-07 07:51:03 +00:00
public void populateWithOrderedConversations ( final List < Conversation > list ) {
populateWithOrderedConversations ( list , true , true ) ;
}
2014-09-02 13:51:20 +00:00
2019-10-07 07:51:03 +00:00
public void populateWithOrderedConversations ( final List < Conversation > list , final boolean includeNoFileUpload ) {
2019-01-11 16:19:54 +00:00
populateWithOrderedConversations ( list , includeNoFileUpload , true ) ;
}
2019-10-07 07:51:03 +00:00
public void populateWithOrderedConversations ( final List < Conversation > list , final boolean includeNoFileUpload , final boolean sort ) {
2019-01-11 16:19:54 +00:00
final List < String > orderedUuids ;
if ( sort ) {
orderedUuids = null ;
} else {
orderedUuids = new ArrayList < > ( ) ;
2019-10-07 07:51:03 +00:00
for ( Conversation conversation : list ) {
2019-01-11 16:19:54 +00:00
orderedUuids . add ( conversation . getUuid ( ) ) ;
}
}
2019-10-07 07:51:03 +00:00
list . clear ( ) ;
if ( includeNoFileUpload ) {
list . addAll ( getConversations ( ) ) ;
} else {
for ( Conversation conversation : getConversations ( ) ) {
if ( conversation . getMode ( ) = = Conversation . MODE_SINGLE
| | ( conversation . getAccount ( ) . httpUploadAvailable ( ) & & conversation . getMucOptions ( ) . participating ( ) ) ) {
list . add ( conversation ) ;
}
}
}
try {
if ( orderedUuids ! = null ) {
2019-01-11 16:19:54 +00:00
Collections . sort ( list , ( a , b ) - > {
final int indexA = orderedUuids . indexOf ( a . getUuid ( ) ) ;
final int indexB = orderedUuids . indexOf ( b . getUuid ( ) ) ;
if ( indexA = = - 1 | | indexB = = - 1 | | indexA = = indexB ) {
return a . compareTo ( b ) ;
}
return indexA - indexB ;
} ) ;
} else {
Collections . sort ( list ) ;
}
2019-10-07 07:51:03 +00:00
} catch ( IllegalArgumentException e ) {
//ignore
}
}
public void loadMoreMessages ( final Conversation conversation , final long timestamp , final OnMoreMessagesLoaded callback ) {
if ( XmppConnectionService . this . getMessageArchiveService ( ) . queryInProgress ( conversation , callback ) ) {
return ;
} else if ( timestamp = = 0 ) {
return ;
}
Log . d ( Config . LOGTAG , " load more messages for " + conversation . getName ( ) + " prior to " + MessageGenerator . getTimestamp ( timestamp ) ) ;
final Runnable runnable = ( ) - > {
final Account account = conversation . getAccount ( ) ;
List < Message > messages = databaseBackend . getMessages ( conversation , 50 , timestamp ) ;
if ( messages . size ( ) > 0 ) {
conversation . addAll ( 0 , messages ) ;
callback . onMoreMessagesLoaded ( messages . size ( ) , conversation ) ;
} else if ( conversation . hasMessagesLeftOnServer ( )
& & account . isOnlineAndConnected ( )
& & conversation . getLastClearHistory ( ) . getTimestamp ( ) = = 0 ) {
final boolean mamAvailable ;
if ( conversation . getMode ( ) = = Conversation . MODE_SINGLE ) {
mamAvailable = account . getXmppConnection ( ) . getFeatures ( ) . mam ( ) & & ! conversation . getContact ( ) . isBlocked ( ) ;
} else {
mamAvailable = conversation . getMucOptions ( ) . mamSupport ( ) ;
}
if ( mamAvailable ) {
MessageArchiveService . Query query = getMessageArchiveService ( ) . query ( conversation , new MamReference ( 0 ) , timestamp , false ) ;
if ( query ! = null ) {
query . setCallback ( callback ) ;
callback . informUser ( R . string . fetching_history_from_server ) ;
} else {
callback . informUser ( R . string . not_fetching_history_retention_period ) ;
}
}
}
} ;
mDatabaseReaderExecutor . execute ( runnable ) ;
}
public List < Account > getAccounts ( ) {
return this . accounts ;
}
2014-06-03 13:48:51 +00:00
2018-11-29 18:27:18 +00:00
/ * *
* This will find all conferences with the contact as member and also the conference that is the contact ( that ' fake ' contact is used to store the avatar )
* /
2019-10-07 07:51:03 +00:00
public List < Conversation > findAllConferencesWith ( Contact contact ) {
2019-10-26 11:23:27 +00:00
final ArrayList < Conversation > results = new ArrayList < > ( ) ;
2019-10-07 07:51:03 +00:00
for ( final Conversation c : conversations ) {
2019-10-26 11:23:27 +00:00
if ( c . getMode ( ) ! = Conversation . MODE_MULTI ) {
continue ;
}
final MucOptions mucOptions = c . getMucOptions ( ) ;
if ( c . getJid ( ) . asBareJid ( ) . equals ( contact . getJid ( ) . asBareJid ( ) ) | | ( mucOptions ! = null & & mucOptions . isContactInRoom ( contact ) ) ) {
2019-10-07 07:51:03 +00:00
results . add ( c ) ;
}
}
return results ;
}
public Conversation find ( final Iterable < Conversation > haystack , final Contact contact ) {
for ( final Conversation conversation : haystack ) {
if ( conversation . getContact ( ) = = contact ) {
return conversation ;
}
}
return null ;
}
public Conversation find ( final Iterable < Conversation > haystack , final Account account , final Jid jid ) {
if ( jid = = null ) {
return null ;
}
for ( final Conversation conversation : haystack ) {
if ( ( account = = null | | conversation . getAccount ( ) = = account )
& & ( conversation . getJid ( ) . asBareJid ( ) . equals ( jid . asBareJid ( ) ) ) ) {
return conversation ;
}
}
return null ;
}
public boolean isConversationsListEmpty ( final Conversation ignore ) {
synchronized ( this . conversations ) {
final int size = this . conversations . size ( ) ;
return size = = 0 | | size = = 1 & & this . conversations . get ( 0 ) = = ignore ;
}
}
public boolean isConversationStillOpen ( final Conversation conversation ) {
synchronized ( this . conversations ) {
for ( Conversation current : this . conversations ) {
if ( current = = conversation ) {
return true ;
}
}
}
return false ;
}
public Conversation findOrCreateConversation ( Account account , Jid jid , boolean muc , final boolean async ) {
return this . findOrCreateConversation ( account , jid , muc , false , async ) ;
}
public Conversation findOrCreateConversation ( final Account account , final Jid jid , final boolean muc , final boolean joinAfterCreate , final boolean async ) {
return this . findOrCreateConversation ( account , jid , muc , joinAfterCreate , null , async ) ;
}
public Conversation findOrCreateConversation ( final Account account , final Jid jid , final boolean muc , final boolean joinAfterCreate , final MessageArchiveService . Query query , final boolean async ) {
synchronized ( this . conversations ) {
Conversation conversation = find ( account , jid ) ;
if ( conversation ! = null ) {
return conversation ;
}
conversation = databaseBackend . findConversation ( account , jid ) ;
final boolean loadMessagesFromDb ;
if ( conversation ! = null ) {
conversation . setStatus ( Conversation . STATUS_AVAILABLE ) ;
conversation . setAccount ( account ) ;
if ( muc ) {
conversation . setMode ( Conversation . MODE_MULTI ) ;
conversation . setContactJid ( jid ) ;
} else {
conversation . setMode ( Conversation . MODE_SINGLE ) ;
conversation . setContactJid ( jid . asBareJid ( ) ) ;
}
databaseBackend . updateConversation ( conversation ) ;
loadMessagesFromDb = conversation . messagesLoaded . compareAndSet ( true , false ) ;
} else {
String conversationName ;
Contact contact = account . getRoster ( ) . getContact ( jid ) ;
if ( contact ! = null ) {
conversationName = contact . getDisplayName ( ) ;
} else {
conversationName = jid . getLocal ( ) ;
}
if ( muc ) {
conversation = new Conversation ( conversationName , account , jid ,
Conversation . MODE_MULTI ) ;
} else {
conversation = new Conversation ( conversationName , account , jid . asBareJid ( ) ,
Conversation . MODE_SINGLE ) ;
}
this . databaseBackend . createConversation ( conversation ) ;
loadMessagesFromDb = false ;
}
final Conversation c = conversation ;
final Runnable runnable = ( ) - > {
if ( loadMessagesFromDb ) {
c . addAll ( 0 , databaseBackend . getMessages ( c , Config . PAGE_SIZE ) ) ;
updateConversationUi ( ) ;
c . messagesLoaded . set ( true ) ;
}
if ( account . getXmppConnection ( ) ! = null
& & ! c . getContact ( ) . isBlocked ( )
& & account . getXmppConnection ( ) . getFeatures ( ) . mam ( )
& & ! muc ) {
if ( query = = null ) {
mMessageArchiveService . query ( c ) ;
} else {
if ( query . getConversation ( ) = = null ) {
mMessageArchiveService . query ( c , query . getStart ( ) , query . isCatchup ( ) ) ;
}
}
}
if ( joinAfterCreate ) {
joinMuc ( c ) ;
}
} ;
if ( async ) {
mDatabaseReaderExecutor . execute ( runnable ) ;
} else {
runnable . run ( ) ;
}
this . conversations . add ( conversation ) ;
updateConversationUi ( ) ;
return conversation ;
}
}
public void archiveConversation ( Conversation conversation ) {
archiveConversation ( conversation , true ) ;
}
private void archiveConversation ( Conversation conversation , final boolean maySynchronizeWithBookmarks ) {
getNotificationService ( ) . clear ( conversation ) ;
conversation . setStatus ( Conversation . STATUS_ARCHIVED ) ;
conversation . setNextMessage ( null ) ;
synchronized ( this . conversations ) {
getMessageArchiveService ( ) . kill ( conversation ) ;
if ( conversation . getMode ( ) = = Conversation . MODE_MULTI ) {
if ( conversation . getAccount ( ) . getStatus ( ) = = Account . State . ONLINE ) {
final Bookmark bookmark = conversation . getBookmark ( ) ;
if ( maySynchronizeWithBookmarks & & bookmark ! = null & & synchronizeWithBookmarks ( ) ) {
if ( conversation . getMucOptions ( ) . getError ( ) = = MucOptions . Error . DESTROYED ) {
Account account = bookmark . getAccount ( ) ;
bookmark . setConversation ( null ) ;
deleteBookmark ( account , bookmark ) ;
} else if ( bookmark . autojoin ( ) ) {
bookmark . setAutojoin ( false ) ;
createBookmark ( bookmark . getAccount ( ) , bookmark ) ;
}
}
}
leaveMuc ( conversation ) ;
} else {
if ( conversation . getContact ( ) . getOption ( Contact . Options . PENDING_SUBSCRIPTION_REQUEST ) ) {
stopPresenceUpdatesTo ( conversation . getContact ( ) ) ;
}
}
updateConversation ( conversation ) ;
this . conversations . remove ( conversation ) ;
updateConversationUi ( ) ;
}
}
public void stopPresenceUpdatesTo ( Contact contact ) {
2018-11-18 09:34:14 +00:00
Log . d ( Config . LOGTAG , " Canceling presence request from " + contact . getJid ( ) . toString ( ) ) ;
sendPresencePacket ( contact . getAccount ( ) , mPresenceGenerator . stopPresenceUpdatesTo ( contact ) ) ;
contact . resetOption ( Contact . Options . PENDING_SUBSCRIPTION_REQUEST ) ;
}
2019-10-07 07:51:03 +00:00
public void createAccount ( final Account account ) {
account . initAccountServices ( this ) ;
databaseBackend . createAccount ( account ) ;
this . accounts . add ( account ) ;
this . reconnectAccountInBackground ( account ) ;
updateAccountUi ( ) ;
syncEnabledAccountSetting ( ) ;
toggleForegroundService ( ) ;
}
private void syncEnabledAccountSetting ( ) {
final boolean hasEnabledAccounts = hasEnabledAccounts ( ) ;
getPreferences ( ) . edit ( ) . putBoolean ( EventReceiver . SETTING_ENABLED_ACCOUNTS , hasEnabledAccounts ) . apply ( ) ;
toggleSetProfilePictureActivity ( hasEnabledAccounts ) ;
}
private void toggleSetProfilePictureActivity ( final boolean enabled ) {
try {
final ComponentName name = new ComponentName ( this , ChooseAccountForProfilePictureActivity . class ) ;
final int targetState = enabled ? PackageManager . COMPONENT_ENABLED_STATE_ENABLED : PackageManager . COMPONENT_ENABLED_STATE_DISABLED ;
2019-01-13 14:28:24 +00:00
getPackageManager ( ) . setComponentEnabledSetting ( name , targetState , PackageManager . DONT_KILL_APP ) ;
} catch ( IllegalStateException e ) {
2019-10-07 07:51:03 +00:00
Log . d ( Config . LOGTAG , " unable to toggle profile picture actvitiy " ) ;
}
}
2020-06-21 13:40:51 +00:00
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 ) ;
2020-08-19 11:57:33 +00:00
Log . d ( Config . LOGTAG , jid . asBareJid ( ) . toEscapedString ( ) + " : provisioning account " ) ;
2020-06-21 13:40:51 +00:00
createAccount ( account ) ;
}
2019-10-07 07:51:03 +00:00
public void createAccountFromKey ( final String alias , final OnAccountCreated callback ) {
new Thread ( ( ) - > {
try {
final X509Certificate [ ] chain = KeyChain . getCertificateChain ( this , alias ) ;
final X509Certificate cert = chain ! = null & & chain . length > 0 ? chain [ 0 ] : null ;
if ( cert = = null ) {
callback . informUser ( R . string . unable_to_parse_certificate ) ;
return ;
}
Pair < Jid , String > info = CryptoHelper . extractJidAndName ( cert ) ;
if ( info = = null ) {
callback . informUser ( R . string . certificate_does_not_contain_jid ) ;
return ;
}
if ( findAccountByJid ( info . first ) = = null ) {
2020-05-13 07:38:30 +00:00
final Account account = new Account ( info . first , " " ) ;
2019-10-07 07:51:03 +00:00
account . setPrivateKeyAlias ( alias ) ;
account . setOption ( Account . OPTION_DISABLED , true ) ;
2020-05-13 11:52:05 +00:00
account . setOption ( Account . OPTION_FIXED_USERNAME , true ) ;
2019-10-07 07:51:03 +00:00
account . setDisplayName ( info . second ) ;
createAccount ( account ) ;
callback . onAccountCreated ( account ) ;
if ( Config . X509_VERIFICATION ) {
try {
2020-05-17 08:24:46 +00:00
getMemorizingTrustManager ( ) . getNonInteractive ( account . getServer ( ) ) . checkClientTrusted ( chain , " RSA " ) ;
2019-10-07 07:51:03 +00:00
} catch ( CertificateException e ) {
callback . informUser ( R . string . certificate_chain_is_not_trusted ) ;
}
}
} else {
callback . informUser ( R . string . account_already_exists ) ;
}
} catch ( Exception e ) {
callback . informUser ( R . string . unable_to_parse_certificate ) ;
}
} ) . start ( ) ;
}
public void updateKeyInAccount ( final Account account , final String alias ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : update key in account " + alias ) ;
try {
X509Certificate [ ] chain = KeyChain . getCertificateChain ( XmppConnectionService . this , alias ) ;
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " loaded certificate chain " ) ;
Pair < Jid , String > info = CryptoHelper . extractJidAndName ( chain [ 0 ] ) ;
if ( info = = null ) {
showErrorToastInUi ( R . string . certificate_does_not_contain_jid ) ;
return ;
}
if ( account . getJid ( ) . asBareJid ( ) . equals ( info . first ) ) {
account . setPrivateKeyAlias ( alias ) ;
account . setDisplayName ( info . second ) ;
databaseBackend . updateAccount ( account ) ;
if ( Config . X509_VERIFICATION ) {
try {
getMemorizingTrustManager ( ) . getNonInteractive ( ) . checkClientTrusted ( chain , " RSA " ) ;
} catch ( CertificateException e ) {
showErrorToastInUi ( R . string . certificate_chain_is_not_trusted ) ;
}
account . getAxolotlService ( ) . regenerateKeys ( true ) ;
}
} else {
showErrorToastInUi ( R . string . jid_does_not_match_certificate ) ;
}
} catch ( Exception e ) {
e . printStackTrace ( ) ;
}
}
public boolean updateAccount ( final Account account ) {
if ( databaseBackend . updateAccount ( account ) ) {
account . setShowErrorNotification ( true ) ;
this . statusListener . onStatusChanged ( account ) ;
databaseBackend . updateAccount ( account ) ;
reconnectAccountInBackground ( account ) ;
updateAccountUi ( ) ;
getNotificationService ( ) . updateErrorNotification ( ) ;
toggleForegroundService ( ) ;
syncEnabledAccountSetting ( ) ;
2019-11-02 08:43:37 +00:00
mChannelDiscoveryService . cleanCache ( ) ;
2019-10-07 07:51:03 +00:00
return true ;
} else {
return false ;
}
}
public void updateAccountPasswordOnServer ( final Account account , final String newPassword , final OnAccountPasswordChanged callback ) {
final IqPacket iq = getIqGenerator ( ) . generateSetPassword ( account , newPassword ) ;
sendIqPacket ( account , iq , ( a , packet ) - > {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
a . setPassword ( newPassword ) ;
a . setOption ( Account . OPTION_MAGIC_CREATE , false ) ;
databaseBackend . updateAccount ( a ) ;
callback . onPasswordChangeSucceeded ( ) ;
} else {
callback . onPasswordChangeFailed ( ) ;
}
} ) ;
}
public void deleteAccount ( final Account account ) {
final boolean connected = account . getStatus ( ) = = Account . State . ONLINE ;
synchronized ( this . conversations ) {
if ( connected ) {
2019-09-28 23:40:40 +00:00
account . getAxolotlService ( ) . deleteOmemoIdentity ( ) ;
}
for ( final Conversation conversation : conversations ) {
if ( conversation . getAccount ( ) = = account ) {
if ( conversation . getMode ( ) = = Conversation . MODE_MULTI ) {
if ( connected ) {
leaveMuc ( conversation ) ;
}
}
conversations . remove ( conversation ) ;
mNotificationService . clear ( conversation ) ;
}
}
2019-10-07 07:51:03 +00:00
if ( account . getXmppConnection ( ) ! = null ) {
new Thread ( ( ) - > disconnect ( account , ! connected ) ) . start ( ) ;
2019-06-30 17:54:07 +00:00
}
2019-10-07 07:51:03 +00:00
final Runnable runnable = ( ) - > {
if ( ! databaseBackend . deleteAccount ( account ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : unable to delete account " ) ;
2019-06-18 11:20:24 +00:00
}
2019-10-07 07:51:03 +00:00
} ;
mDatabaseWriterExecutor . execute ( runnable ) ;
this . accounts . remove ( account ) ;
this . mRosterSyncTaskManager . clear ( account ) ;
updateAccountUi ( ) ;
mNotificationService . updateErrorNotification ( ) ;
syncEnabledAccountSetting ( ) ;
toggleForegroundService ( ) ;
}
2019-06-18 11:20:24 +00:00
}
2019-10-07 07:51:03 +00:00
public void setOnConversationListChangedListener ( OnConversationUpdate listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
remainingListeners = checkListeners ( ) ;
if ( ! this . mOnConversationUpdates . add ( listener ) ) {
Log . w ( Config . LOGTAG , listener . getClass ( ) . getName ( ) + " is already registered as ConversationListChangedListener " ) ;
}
this . mNotificationService . setIsInForeground ( this . mOnConversationUpdates . size ( ) > 0 ) ;
2019-06-30 19:57:37 +00:00
}
2019-10-07 07:51:03 +00:00
if ( remainingListeners ) {
switchToForeground ( ) ;
2019-06-30 19:57:37 +00:00
}
2019-10-07 07:51:03 +00:00
}
2018-12-09 17:25:11 +00:00
2019-10-07 07:51:03 +00:00
public void removeOnConversationListChangedListener ( OnConversationUpdate listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
this . mOnConversationUpdates . remove ( listener ) ;
this . mNotificationService . setIsInForeground ( this . mOnConversationUpdates . size ( ) > 0 ) ;
remainingListeners = checkListeners ( ) ;
}
if ( remainingListeners ) {
switchToBackground ( ) ;
}
}
2015-10-22 09:20:36 +00:00
2019-10-07 07:51:03 +00:00
public void setOnShowErrorToastListener ( OnShowErrorToast listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
remainingListeners = checkListeners ( ) ;
if ( ! this . mOnShowErrorToasts . add ( listener ) ) {
Log . w ( Config . LOGTAG , listener . getClass ( ) . getName ( ) + " is already registered as OnShowErrorToastListener " ) ;
}
}
if ( remainingListeners ) {
switchToForeground ( ) ;
}
}
2015-10-22 09:20:36 +00:00
2019-10-07 07:51:03 +00:00
public void removeOnShowErrorToastListener ( OnShowErrorToast onShowErrorToast ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
this . mOnShowErrorToasts . remove ( onShowErrorToast ) ;
remainingListeners = checkListeners ( ) ;
}
if ( remainingListeners ) {
switchToBackground ( ) ;
}
}
2019-06-18 16:09:44 +00:00
2019-10-07 07:51:03 +00:00
public void setOnAccountListChangedListener ( OnAccountUpdate listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
remainingListeners = checkListeners ( ) ;
if ( ! this . mOnAccountUpdates . add ( listener ) ) {
Log . w ( Config . LOGTAG , listener . getClass ( ) . getName ( ) + " is already registered as OnAccountListChangedtListener " ) ;
2019-06-30 19:57:37 +00:00
}
2019-10-07 07:51:03 +00:00
}
if ( remainingListeners ) {
switchToForeground ( ) ;
}
}
2014-03-11 14:44:22 +00:00
2019-10-07 07:51:03 +00:00
public void removeOnAccountListChangedListener ( OnAccountUpdate listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
this . mOnAccountUpdates . remove ( listener ) ;
remainingListeners = checkListeners ( ) ;
}
if ( remainingListeners ) {
switchToBackground ( ) ;
}
}
public void setOnCaptchaRequestedListener ( OnCaptchaRequested listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
remainingListeners = checkListeners ( ) ;
if ( ! this . mOnCaptchaRequested . add ( listener ) ) {
Log . w ( Config . LOGTAG , listener . getClass ( ) . getName ( ) + " is already registered as OnCaptchaRequestListener " ) ;
2019-06-24 16:16:03 +00:00
}
2019-10-07 07:51:03 +00:00
}
if ( remainingListeners ) {
switchToForeground ( ) ;
}
2019-06-25 16:15:51 +00:00
}
2019-06-24 16:16:03 +00:00
2019-10-07 07:51:03 +00:00
public void removeOnCaptchaRequestedListener ( OnCaptchaRequested listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
this . mOnCaptchaRequested . remove ( listener ) ;
remainingListeners = checkListeners ( ) ;
}
if ( remainingListeners ) {
switchToBackground ( ) ;
}
2019-06-24 16:16:03 +00:00
}
2019-10-07 07:51:03 +00:00
public void setOnRosterUpdateListener ( final OnRosterUpdate listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
remainingListeners = checkListeners ( ) ;
if ( ! this . mOnRosterUpdates . add ( listener ) ) {
Log . w ( Config . LOGTAG , listener . getClass ( ) . getName ( ) + " is already registered as OnRosterUpdateListener " ) ;
2019-06-24 16:16:03 +00:00
}
2019-10-07 07:51:03 +00:00
}
if ( remainingListeners ) {
switchToForeground ( ) ;
}
2019-06-24 16:16:03 +00:00
}
2019-10-07 07:51:03 +00:00
public void removeOnRosterUpdateListener ( final OnRosterUpdate listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
this . mOnRosterUpdates . remove ( listener ) ;
remainingListeners = checkListeners ( ) ;
}
if ( remainingListeners ) {
switchToBackground ( ) ;
}
2018-09-15 17:38:45 +00:00
}
2019-10-07 07:51:03 +00:00
public void setOnUpdateBlocklistListener ( final OnUpdateBlocklist listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
remainingListeners = checkListeners ( ) ;
if ( ! this . mOnUpdateBlocklist . add ( listener ) ) {
Log . w ( Config . LOGTAG , listener . getClass ( ) . getName ( ) + " is already registered as OnUpdateBlocklistListener " ) ;
}
}
if ( remainingListeners ) {
switchToForeground ( ) ;
}
2018-09-15 19:10:17 +00:00
}
2019-10-07 07:51:03 +00:00
public void removeOnUpdateBlocklistListener ( final OnUpdateBlocklist listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
this . mOnUpdateBlocklist . remove ( listener ) ;
remainingListeners = checkListeners ( ) ;
}
if ( remainingListeners ) {
switchToBackground ( ) ;
}
2018-09-15 17:38:45 +00:00
}
2019-10-07 07:51:03 +00:00
public void setOnKeyStatusUpdatedListener ( final OnKeyStatusUpdated listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
remainingListeners = checkListeners ( ) ;
if ( ! this . mOnKeyStatusUpdated . add ( listener ) ) {
Log . w ( Config . LOGTAG , listener . getClass ( ) . getName ( ) + " is already registered as OnKeyStatusUpdateListener " ) ;
}
}
if ( remainingListeners ) {
switchToForeground ( ) ;
}
}
public void removeOnNewKeysAvailableListener ( final OnKeyStatusUpdated listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
this . mOnKeyStatusUpdated . remove ( listener ) ;
remainingListeners = checkListeners ( ) ;
}
if ( remainingListeners ) {
switchToBackground ( ) ;
}
}
2020-04-07 11:15:24 +00:00
public void setOnRtpConnectionUpdateListener ( final OnJingleRtpConnectionUpdate listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
remainingListeners = checkListeners ( ) ;
if ( ! this . onJingleRtpConnectionUpdate . add ( listener ) ) {
Log . w ( Config . LOGTAG , listener . getClass ( ) . getName ( ) + " is already registered as OnJingleRtpConnectionUpdate " ) ;
}
}
if ( remainingListeners ) {
switchToForeground ( ) ;
}
}
public void removeRtpConnectionUpdateListener ( final OnJingleRtpConnectionUpdate listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
this . onJingleRtpConnectionUpdate . remove ( listener ) ;
remainingListeners = checkListeners ( ) ;
}
if ( remainingListeners ) {
switchToBackground ( ) ;
}
}
2019-10-07 07:51:03 +00:00
public void setOnMucRosterUpdateListener ( OnMucRosterUpdate listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
remainingListeners = checkListeners ( ) ;
if ( ! this . mOnMucRosterUpdate . add ( listener ) ) {
Log . w ( Config . LOGTAG , listener . getClass ( ) . getName ( ) + " is already registered as OnMucRosterListener " ) ;
}
}
if ( remainingListeners ) {
switchToForeground ( ) ;
}
}
public void removeOnMucRosterUpdateListener ( final OnMucRosterUpdate listener ) {
final boolean remainingListeners ;
synchronized ( LISTENER_LOCK ) {
this . mOnMucRosterUpdate . remove ( listener ) ;
remainingListeners = checkListeners ( ) ;
}
if ( remainingListeners ) {
switchToBackground ( ) ;
}
}
public boolean checkListeners ( ) {
return ( this . mOnAccountUpdates . size ( ) = = 0
& & this . mOnConversationUpdates . size ( ) = = 0
& & this . mOnRosterUpdates . size ( ) = = 0
& & this . mOnCaptchaRequested . size ( ) = = 0
& & this . mOnMucRosterUpdate . size ( ) = = 0
& & this . mOnUpdateBlocklist . size ( ) = = 0
& & this . mOnShowErrorToasts . size ( ) = = 0
2020-04-07 11:15:24 +00:00
& & this . onJingleRtpConnectionUpdate . size ( ) = = 0
2019-10-07 07:51:03 +00:00
& & this . mOnKeyStatusUpdated . size ( ) = = 0 ) ;
}
private void switchToForeground ( ) {
final boolean broadcastLastActivity = broadcastLastActivity ( ) ;
for ( Conversation conversation : getConversations ( ) ) {
if ( conversation . getMode ( ) = = Conversation . MODE_MULTI ) {
conversation . getMucOptions ( ) . resetChatState ( ) ;
} else {
2020-01-18 11:08:03 +00:00
conversation . setIncomingChatState ( Config . DEFAULT_CHAT_STATE ) ;
2019-10-07 07:51:03 +00:00
}
}
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
}
}
}
}
Log . d ( Config . LOGTAG , " app switched into foreground " ) ;
}
private void switchToBackground ( ) {
final boolean broadcastLastActivity = broadcastLastActivity ( ) ;
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 ( ) ;
}
}
}
}
this . mNotificationService . setIsInForeground ( false ) ;
Log . d ( Config . LOGTAG , " app switched into background " ) ;
}
private void connectMultiModeConversations ( Account account ) {
List < Conversation > conversations = getConversations ( ) ;
for ( Conversation conversation : conversations ) {
if ( conversation . getMode ( ) = = Conversation . MODE_MULTI & & conversation . getAccount ( ) = = account ) {
joinMuc ( conversation ) ;
}
}
}
public void mucSelfPingAndRejoin ( final Conversation conversation ) {
final Account account = conversation . getAccount ( ) ;
synchronized ( account . inProgressConferenceJoins ) {
if ( account . inProgressConferenceJoins . contains ( conversation ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : canceling muc self ping because join is already under way " ) ;
return ;
}
}
synchronized ( account . inProgressConferencePings ) {
if ( ! account . inProgressConferencePings . add ( conversation ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : canceling muc self ping because ping is already under way " ) ;
return ;
}
}
final Jid self = conversation . getMucOptions ( ) . getSelf ( ) . getFullJid ( ) ;
final IqPacket ping = new IqPacket ( IqPacket . TYPE . GET ) ;
ping . setTo ( self ) ;
ping . addChild ( " ping " , Namespace . PING ) ;
sendIqPacket ( conversation . getAccount ( ) , ping , ( a , response ) - > {
if ( response . getType ( ) = = IqPacket . TYPE . ERROR ) {
Element error = response . findChild ( " error " ) ;
if ( error = = null | | error . hasChild ( " service-unavailable " ) | | error . hasChild ( " feature-not-implemented " ) | | error . hasChild ( " item-not-found " ) ) {
Log . d ( Config . LOGTAG , a . getJid ( ) . asBareJid ( ) + " : ping to " + self + " came back as ignorable error " ) ;
} else {
Log . d ( Config . LOGTAG , a . getJid ( ) . asBareJid ( ) + " : ping to " + self + " failed. attempting rejoin " ) ;
joinMuc ( conversation ) ;
}
} else if ( response . getType ( ) = = IqPacket . TYPE . RESULT ) {
Log . d ( Config . LOGTAG , a . getJid ( ) . asBareJid ( ) + " : ping to " + self + " came back fine " ) ;
}
synchronized ( account . inProgressConferencePings ) {
account . inProgressConferencePings . remove ( conversation ) ;
}
} ) ;
}
public void joinMuc ( Conversation conversation ) {
joinMuc ( conversation , null , false ) ;
}
public void joinMuc ( Conversation conversation , boolean followedInvite ) {
joinMuc ( conversation , null , followedInvite ) ;
}
private void joinMuc ( Conversation conversation , final OnConferenceJoined onConferenceJoined ) {
joinMuc ( conversation , onConferenceJoined , false ) ;
}
private void joinMuc ( Conversation conversation , final OnConferenceJoined onConferenceJoined , final boolean followedInvite ) {
final Account account = conversation . getAccount ( ) ;
synchronized ( account . pendingConferenceJoins ) {
account . pendingConferenceJoins . remove ( conversation ) ;
}
synchronized ( account . pendingConferenceLeaves ) {
account . pendingConferenceLeaves . remove ( conversation ) ;
}
if ( account . getStatus ( ) = = Account . State . ONLINE ) {
synchronized ( account . inProgressConferenceJoins ) {
account . inProgressConferenceJoins . add ( conversation ) ;
}
if ( Config . MUC_LEAVE_BEFORE_JOIN ) {
sendPresencePacket ( account , mPresenceGenerator . leave ( conversation . getMucOptions ( ) ) ) ;
}
conversation . resetMucOptions ( ) ;
if ( onConferenceJoined ! = null ) {
conversation . getMucOptions ( ) . flagNoAutoPushConfiguration ( ) ;
}
conversation . setHasMessagesLeftOnServer ( false ) ;
fetchConferenceConfiguration ( conversation , new OnConferenceConfigurationFetched ( ) {
private void join ( Conversation conversation ) {
Account account = conversation . getAccount ( ) ;
final MucOptions mucOptions = conversation . getMucOptions ( ) ;
if ( mucOptions . nonanonymous ( ) & & ! mucOptions . membersOnly ( ) & & ! conversation . getBooleanAttribute ( " accept_non_anonymous " , false ) ) {
synchronized ( account . inProgressConferenceJoins ) {
account . inProgressConferenceJoins . remove ( conversation ) ;
}
mucOptions . setError ( MucOptions . Error . NON_ANONYMOUS ) ;
updateConversationUi ( ) ;
if ( onConferenceJoined ! = null ) {
onConferenceJoined . onConferenceJoined ( conversation ) ;
}
return ;
}
final Jid joinJid = mucOptions . getSelf ( ) . getFullJid ( ) ;
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) . toString ( ) + " : joining conversation " + joinJid . toString ( ) ) ;
PresencePacket packet = mPresenceGenerator . selfPresence ( account , Presence . Status . ONLINE , mucOptions . nonanonymous ( ) | | onConferenceJoined ! = null ) ;
packet . setTo ( joinJid ) ;
Element x = packet . addChild ( " x " , " http://jabber.org/protocol/muc " ) ;
if ( conversation . getMucOptions ( ) . getPassword ( ) ! = null ) {
x . addChild ( " password " ) . setContent ( mucOptions . getPassword ( ) ) ;
}
if ( mucOptions . mamSupport ( ) ) {
// Use MAM instead of the limited muc history to get history
x . addChild ( " history " ) . setAttribute ( " maxchars " , " 0 " ) ;
} else {
// Fallback to muc history
x . addChild ( " history " ) . setAttribute ( " since " , PresenceGenerator . getTimestamp ( conversation . getLastMessageTransmitted ( ) . getTimestamp ( ) ) ) ;
}
sendPresencePacket ( account , packet ) ;
if ( onConferenceJoined ! = null ) {
onConferenceJoined . onConferenceJoined ( conversation ) ;
}
if ( ! joinJid . equals ( conversation . getJid ( ) ) ) {
conversation . setContactJid ( joinJid ) ;
databaseBackend . updateConversation ( conversation ) ;
}
if ( mucOptions . mamSupport ( ) ) {
getMessageArchiveService ( ) . catchupMUC ( conversation ) ;
}
if ( mucOptions . isPrivateAndNonAnonymous ( ) ) {
fetchConferenceMembers ( conversation ) ;
if ( followedInvite ) {
final Bookmark bookmark = conversation . getBookmark ( ) ;
if ( bookmark ! = null ) {
if ( ! bookmark . autojoin ( ) ) {
bookmark . setAutojoin ( true ) ;
createBookmark ( account , bookmark ) ;
}
} else {
saveConversationAsBookmark ( conversation , null ) ;
}
}
}
synchronized ( account . inProgressConferenceJoins ) {
account . inProgressConferenceJoins . remove ( conversation ) ;
sendUnsentMessages ( conversation ) ;
}
}
@Override
public void onConferenceConfigurationFetched ( Conversation conversation ) {
if ( conversation . getStatus ( ) = = Conversation . STATUS_ARCHIVED ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : conversation ( " + conversation . getJid ( ) + " ) got archived before IQ result " ) ;
return ;
}
join ( conversation ) ;
}
@Override
public void onFetchFailed ( final Conversation conversation , Element error ) {
if ( conversation . getStatus ( ) = = Conversation . STATUS_ARCHIVED ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : conversation ( " + conversation . getJid ( ) + " ) got archived before IQ result " ) ;
return ;
}
if ( error ! = null & & " remote-server-not-found " . equals ( error . getName ( ) ) ) {
synchronized ( account . inProgressConferenceJoins ) {
account . inProgressConferenceJoins . remove ( conversation ) ;
}
conversation . getMucOptions ( ) . setError ( MucOptions . Error . SERVER_NOT_FOUND ) ;
updateConversationUi ( ) ;
} else {
join ( conversation ) ;
fetchConferenceConfiguration ( conversation ) ;
}
}
} ) ;
updateConversationUi ( ) ;
} else {
synchronized ( account . pendingConferenceJoins ) {
account . pendingConferenceJoins . add ( conversation ) ;
}
conversation . resetMucOptions ( ) ;
conversation . setHasMessagesLeftOnServer ( false ) ;
updateConversationUi ( ) ;
}
}
2020-06-12 18:06:49 +00:00
2019-10-07 07:51:03 +00:00
private void fetchConferenceMembers ( final Conversation conversation ) {
final Account account = conversation . getAccount ( ) ;
final AxolotlService axolotlService = account . getAxolotlService ( ) ;
final String [ ] affiliations = { " member " , " admin " , " owner " } ;
OnIqPacketReceived callback = new OnIqPacketReceived ( ) {
private int i = 0 ;
private boolean success = true ;
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
final boolean omemoEnabled = conversation . getNextEncryption ( ) = = Message . ENCRYPTION_AXOLOTL ;
Element query = packet . query ( " http://jabber.org/protocol/muc#admin " ) ;
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT & & query ! = null ) {
for ( Element child : query . getChildren ( ) ) {
if ( " item " . equals ( child . getName ( ) ) ) {
MucOptions . User user = AbstractParser . parseItem ( conversation , child ) ;
if ( ! user . realJidMatchesAccount ( ) ) {
boolean isNew = conversation . getMucOptions ( ) . updateUser ( user ) ;
Contact contact = user . getContact ( ) ;
if ( omemoEnabled
& & isNew
& & user . getRealJid ( ) ! = null
& & ( contact = = null | | ! contact . mutualPresenceSubscription ( ) )
& & axolotlService . hasEmptyDeviceList ( user . getRealJid ( ) ) ) {
axolotlService . fetchDeviceIds ( user . getRealJid ( ) ) ;
}
}
}
}
} else {
success = false ;
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : could not request affiliation " + affiliations [ i ] + " in " + conversation . getJid ( ) . asBareJid ( ) ) ;
}
+ + i ;
if ( i > = affiliations . length ) {
List < Jid > members = conversation . getMucOptions ( ) . getMembers ( true ) ;
if ( success ) {
List < Jid > cryptoTargets = conversation . getAcceptedCryptoTargets ( ) ;
boolean changed = false ;
for ( ListIterator < Jid > iterator = cryptoTargets . listIterator ( ) ; iterator . hasNext ( ) ; ) {
Jid jid = iterator . next ( ) ;
2020-05-18 07:14:57 +00:00
if ( ! members . contains ( jid ) & & ! members . contains ( jid . getDomain ( ) ) ) {
2019-10-07 07:51:03 +00:00
iterator . remove ( ) ;
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : removed " + jid + " from crypto targets of " + conversation . getName ( ) ) ;
changed = true ;
}
}
if ( changed ) {
conversation . setAcceptedCryptoTargets ( cryptoTargets ) ;
updateConversation ( conversation ) ;
}
}
getAvatarService ( ) . clear ( conversation ) ;
updateMucRosterUi ( ) ;
updateConversationUi ( ) ;
}
}
} ;
for ( String affiliation : affiliations ) {
sendIqPacket ( account , mIqGenerator . queryAffiliation ( conversation , affiliation ) , callback ) ;
}
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : fetching members for " + conversation . getName ( ) ) ;
}
public void providePasswordForMuc ( Conversation conversation , String password ) {
if ( conversation . getMode ( ) = = Conversation . MODE_MULTI ) {
conversation . getMucOptions ( ) . setPassword ( password ) ;
if ( conversation . getBookmark ( ) ! = null ) {
final Bookmark bookmark = conversation . getBookmark ( ) ;
if ( synchronizeWithBookmarks ( ) ) {
bookmark . setAutojoin ( true ) ;
}
createBookmark ( conversation . getAccount ( ) , bookmark ) ;
}
updateConversation ( conversation ) ;
joinMuc ( conversation ) ;
}
}
private boolean hasEnabledAccounts ( ) {
if ( this . accounts = = null ) {
return false ;
}
for ( Account account : this . accounts ) {
if ( account . isEnabled ( ) ) {
return true ;
}
}
return false ;
}
public void getAttachments ( final Conversation conversation , int limit , final OnMediaLoaded onMediaLoaded ) {
getAttachments ( conversation . getAccount ( ) , conversation . getJid ( ) . asBareJid ( ) , limit , onMediaLoaded ) ;
}
public void getAttachments ( final Account account , final Jid jid , final int limit , final OnMediaLoaded onMediaLoaded ) {
getAttachments ( account . getUuid ( ) , jid . asBareJid ( ) , limit , onMediaLoaded ) ;
}
public void getAttachments ( final String account , final Jid jid , final int limit , final OnMediaLoaded onMediaLoaded ) {
new Thread ( ( ) - > onMediaLoaded . onMediaLoaded ( fileBackend . convertToAttachments ( databaseBackend . getRelativeFilePaths ( account , jid , limit ) ) ) ) . start ( ) ;
}
public void persistSelfNick ( MucOptions . User self ) {
final Conversation conversation = self . getConversation ( ) ;
final boolean tookProposedNickFromBookmark = conversation . getMucOptions ( ) . isTookProposedNickFromBookmark ( ) ;
Jid full = self . getFullJid ( ) ;
if ( ! full . equals ( conversation . getJid ( ) ) ) {
Log . d ( Config . LOGTAG , " nick changed. updating " ) ;
conversation . setContactJid ( full ) ;
databaseBackend . updateConversation ( conversation ) ;
}
final Bookmark bookmark = conversation . getBookmark ( ) ;
final String bookmarkedNick = bookmark = = null ? null : bookmark . getNick ( ) ;
if ( bookmark ! = null & & ( tookProposedNickFromBookmark | | TextUtils . isEmpty ( bookmarkedNick ) ) & & ! full . getResource ( ) . equals ( bookmarkedNick ) ) {
final Account account = conversation . getAccount ( ) ;
final String defaultNick = MucOptions . defaultNick ( account ) ;
if ( TextUtils . isEmpty ( bookmarkedNick ) & & full . getResource ( ) . equals ( defaultNick ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : do not overwrite empty bookmark nick with default nick for " + conversation . getJid ( ) . asBareJid ( ) ) ;
return ;
}
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : persist nick ' " + full . getResource ( ) + " ' into bookmark for " + conversation . getJid ( ) . asBareJid ( ) ) ;
bookmark . setNick ( full . getResource ( ) ) ;
createBookmark ( bookmark . getAccount ( ) , bookmark ) ;
}
}
public boolean renameInMuc ( final Conversation conversation , final String nick , final UiCallback < Conversation > callback ) {
final MucOptions options = conversation . getMucOptions ( ) ;
final Jid joinJid = options . createJoinJid ( nick ) ;
if ( joinJid = = null ) {
return false ;
}
if ( options . online ( ) ) {
Account account = conversation . getAccount ( ) ;
options . setOnRenameListener ( new OnRenameListener ( ) {
@Override
public void onSuccess ( ) {
callback . success ( conversation ) ;
}
@Override
public void onFailure ( ) {
callback . error ( R . string . nick_in_use , conversation ) ;
}
} ) ;
final PresencePacket packet = mPresenceGenerator . selfPresence ( account , Presence . Status . ONLINE , options . nonanonymous ( ) ) ;
packet . setTo ( joinJid ) ;
sendPresencePacket ( account , packet ) ;
} else {
conversation . setContactJid ( joinJid ) ;
databaseBackend . updateConversation ( conversation ) ;
if ( conversation . getAccount ( ) . getStatus ( ) = = Account . State . ONLINE ) {
Bookmark bookmark = conversation . getBookmark ( ) ;
if ( bookmark ! = null ) {
bookmark . setNick ( nick ) ;
createBookmark ( bookmark . getAccount ( ) , bookmark ) ;
}
joinMuc ( conversation ) ;
}
}
return true ;
}
public void leaveMuc ( Conversation conversation ) {
leaveMuc ( conversation , false ) ;
}
private void leaveMuc ( Conversation conversation , boolean now ) {
final Account account = conversation . getAccount ( ) ;
synchronized ( account . pendingConferenceJoins ) {
account . pendingConferenceJoins . remove ( conversation ) ;
}
synchronized ( account . pendingConferenceLeaves ) {
account . pendingConferenceLeaves . remove ( conversation ) ;
}
if ( account . getStatus ( ) = = Account . State . ONLINE | | now ) {
sendPresencePacket ( conversation . getAccount ( ) , mPresenceGenerator . leave ( conversation . getMucOptions ( ) ) ) ;
conversation . getMucOptions ( ) . setOffline ( ) ;
Bookmark bookmark = conversation . getBookmark ( ) ;
if ( bookmark ! = null ) {
bookmark . setConversation ( null ) ;
}
Log . d ( Config . LOGTAG , conversation . getAccount ( ) . getJid ( ) . asBareJid ( ) + " : leaving muc " + conversation . getJid ( ) ) ;
} else {
synchronized ( account . pendingConferenceLeaves ) {
account . pendingConferenceLeaves . add ( conversation ) ;
}
}
}
public String findConferenceServer ( final Account account ) {
String server ;
if ( account . getXmppConnection ( ) ! = null ) {
server = account . getXmppConnection ( ) . getMucServer ( ) ;
if ( server ! = null ) {
return server ;
}
}
for ( Account other : getAccounts ( ) ) {
if ( other ! = account & & other . getXmppConnection ( ) ! = null ) {
server = other . getXmppConnection ( ) . getMucServer ( ) ;
if ( server ! = null ) {
return server ;
}
}
}
return null ;
}
public void createPublicChannel ( final Account account , final String name , final Jid address , final UiCallback < Conversation > callback ) {
joinMuc ( findOrCreateConversation ( account , address , true , false , true ) , conversation - > {
final Bundle configuration = IqGenerator . defaultChannelConfiguration ( ) ;
if ( ! TextUtils . isEmpty ( name ) ) {
configuration . putString ( " muc#roomconfig_roomname " , name ) ;
}
pushConferenceConfiguration ( conversation , configuration , new OnConfigurationPushed ( ) {
@Override
public void onPushSucceeded ( ) {
saveConversationAsBookmark ( conversation , name ) ;
callback . success ( conversation ) ;
}
@Override
public void onPushFailed ( ) {
if ( conversation . getMucOptions ( ) . getSelf ( ) . getAffiliation ( ) . ranks ( MucOptions . Affiliation . OWNER ) ) {
callback . error ( R . string . unable_to_set_channel_configuration , conversation ) ;
} else {
callback . error ( R . string . joined_an_existing_channel , conversation ) ;
}
}
} ) ;
} ) ;
}
public boolean createAdhocConference ( final Account account ,
final String name ,
final Iterable < Jid > jids ,
final UiCallback < Conversation > callback ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) . toString ( ) + " : creating adhoc conference with " + jids . toString ( ) ) ;
if ( account . getStatus ( ) = = Account . State . ONLINE ) {
try {
String server = findConferenceServer ( account ) ;
if ( server = = null ) {
if ( callback ! = null ) {
callback . error ( R . string . no_conference_server_found , null ) ;
}
return false ;
}
final Jid jid = Jid . of ( CryptoHelper . pronounceable ( getRNG ( ) ) , server , null ) ;
final Conversation conversation = findOrCreateConversation ( account , jid , true , false , true ) ;
joinMuc ( conversation , new OnConferenceJoined ( ) {
@Override
public void onConferenceJoined ( final Conversation conversation ) {
final Bundle configuration = IqGenerator . defaultGroupChatConfiguration ( ) ;
if ( ! TextUtils . isEmpty ( name ) ) {
configuration . putString ( " muc#roomconfig_roomname " , name ) ;
}
pushConferenceConfiguration ( conversation , configuration , new OnConfigurationPushed ( ) {
@Override
public void onPushSucceeded ( ) {
for ( Jid invite : jids ) {
invite ( conversation , invite ) ;
}
for ( String resource : account . getSelfContact ( ) . getPresences ( ) . toResourceArray ( ) ) {
Jid other = account . getJid ( ) . withResource ( resource ) ;
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : sending direct invite to " + other ) ;
directInvite ( conversation , other ) ;
}
saveConversationAsBookmark ( conversation , name ) ;
if ( callback ! = null ) {
callback . success ( conversation ) ;
}
}
@Override
public void onPushFailed ( ) {
archiveConversation ( conversation ) ;
if ( callback ! = null ) {
callback . error ( R . string . conference_creation_failed , conversation ) ;
}
}
} ) ;
}
} ) ;
return true ;
} catch ( IllegalArgumentException e ) {
if ( callback ! = null ) {
callback . error ( R . string . conference_creation_failed , null ) ;
}
return false ;
}
} else {
if ( callback ! = null ) {
callback . error ( R . string . not_connected_try_again , null ) ;
}
return false ;
}
}
public void fetchConferenceConfiguration ( final Conversation conversation ) {
fetchConferenceConfiguration ( conversation , null ) ;
}
public void fetchConferenceConfiguration ( final Conversation conversation , final OnConferenceConfigurationFetched callback ) {
2019-11-02 08:43:37 +00:00
IqPacket request = mIqGenerator . queryDiscoInfo ( conversation . getJid ( ) . asBareJid ( ) ) ;
2019-10-07 07:51:03 +00:00
sendIqPacket ( conversation . getAccount ( ) , request , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
final MucOptions mucOptions = conversation . getMucOptions ( ) ;
final Bookmark bookmark = conversation . getBookmark ( ) ;
final boolean sameBefore = StringUtils . equals ( bookmark = = null ? null : bookmark . getBookmarkName ( ) , mucOptions . getName ( ) ) ;
if ( mucOptions . updateConfiguration ( new ServiceDiscoveryResult ( packet ) ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : muc configuration changed for " + conversation . getJid ( ) . asBareJid ( ) ) ;
updateConversation ( conversation ) ;
}
if ( bookmark ! = null & & ( sameBefore | | bookmark . getBookmarkName ( ) = = null ) ) {
if ( bookmark . setBookmarkName ( StringUtils . nullOnEmpty ( mucOptions . getName ( ) ) ) ) {
createBookmark ( account , bookmark ) ;
}
}
if ( callback ! = null ) {
callback . onConferenceConfigurationFetched ( conversation ) ;
}
updateConversationUi ( ) ;
} else if ( packet . getType ( ) = = IqPacket . TYPE . TIMEOUT ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : received timeout waiting for conference configuration fetch " ) ;
} else {
if ( callback ! = null ) {
callback . onFetchFailed ( conversation , packet . getError ( ) ) ;
}
}
}
} ) ;
}
public void pushNodeConfiguration ( Account account , final String node , final Bundle options , final OnConfigurationPushed callback ) {
pushNodeConfiguration ( account , account . getJid ( ) . asBareJid ( ) , node , options , callback ) ;
}
public void pushNodeConfiguration ( Account account , final Jid jid , final String node , final Bundle options , final OnConfigurationPushed callback ) {
Log . d ( Config . LOGTAG , " pushing node configuration " ) ;
sendIqPacket ( account , mIqGenerator . requestPubsubConfiguration ( jid , node ) , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
Element pubsub = packet . findChild ( " pubsub " , " http://jabber.org/protocol/pubsub#owner " ) ;
Element configuration = pubsub = = null ? null : pubsub . findChild ( " configure " ) ;
Element x = configuration = = null ? null : configuration . findChild ( " x " , Namespace . DATA ) ;
if ( x ! = null ) {
Data data = Data . parse ( x ) ;
data . submit ( options ) ;
sendIqPacket ( account , mIqGenerator . publishPubsubConfiguration ( jid , node , data ) , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT & & callback ! = null ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : successfully changed node configuration for node " + node ) ;
callback . onPushSucceeded ( ) ;
} else if ( packet . getType ( ) = = IqPacket . TYPE . ERROR & & callback ! = null ) {
callback . onPushFailed ( ) ;
}
}
} ) ;
} else if ( callback ! = null ) {
callback . onPushFailed ( ) ;
}
} else if ( packet . getType ( ) = = IqPacket . TYPE . ERROR & & callback ! = null ) {
callback . onPushFailed ( ) ;
}
}
} ) ;
}
public void pushConferenceConfiguration ( final Conversation conversation , final Bundle options , final OnConfigurationPushed callback ) {
if ( options . getString ( " muc#roomconfig_whois " , " moderators " ) . equals ( " anyone " ) ) {
conversation . setAttribute ( " accept_non_anonymous " , true ) ;
updateConversation ( conversation ) ;
}
2020-03-02 10:11:23 +00:00
if ( options . containsKey ( " muc#roomconfig_moderatedroom " ) ) {
final boolean moderated = " 1 " . equals ( options . getString ( " muc#roomconfig_moderatedroom " ) ) ;
options . putString ( " members_by_default " , moderated ? " 0 " : " 1 " ) ;
}
final IqPacket request = new IqPacket ( IqPacket . TYPE . GET ) ;
2019-10-07 07:51:03 +00:00
request . setTo ( conversation . getJid ( ) . asBareJid ( ) ) ;
request . query ( " http://jabber.org/protocol/muc#owner " ) ;
sendIqPacket ( conversation . getAccount ( ) , request , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
2020-03-02 10:11:23 +00:00
final Data data = Data . parse ( packet . query ( ) . findChild ( " x " , Namespace . DATA ) ) ;
2019-10-07 07:51:03 +00:00
data . submit ( options ) ;
2020-03-02 10:11:23 +00:00
final IqPacket set = new IqPacket ( IqPacket . TYPE . SET ) ;
2019-10-07 07:51:03 +00:00
set . setTo ( conversation . getJid ( ) . asBareJid ( ) ) ;
set . query ( " http://jabber.org/protocol/muc#owner " ) . addChild ( data ) ;
sendIqPacket ( account , set , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
if ( callback ! = null ) {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
callback . onPushSucceeded ( ) ;
} else {
callback . onPushFailed ( ) ;
}
}
}
} ) ;
} else {
if ( callback ! = null ) {
callback . onPushFailed ( ) ;
}
}
}
} ) ;
}
public void pushSubjectToConference ( final Conversation conference , final String subject ) {
MessagePacket packet = this . getMessageGenerator ( ) . conferenceSubject ( conference , StringUtils . nullOnEmpty ( subject ) ) ;
this . sendMessagePacket ( conference . getAccount ( ) , packet ) ;
}
public void changeAffiliationInConference ( final Conversation conference , Jid user , final MucOptions . Affiliation affiliation , final OnAffiliationChanged callback ) {
final Jid jid = user . asBareJid ( ) ;
IqPacket request = this . mIqGenerator . changeAffiliation ( conference , jid , affiliation . toString ( ) ) ;
sendIqPacket ( conference . getAccount ( ) , request , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
conference . getMucOptions ( ) . changeAffiliation ( jid , affiliation ) ;
getAvatarService ( ) . clear ( conference ) ;
callback . onAffiliationChangedSuccessful ( jid ) ;
} else {
callback . onAffiliationChangeFailed ( jid , R . string . could_not_change_affiliation ) ;
}
}
} ) ;
}
public void changeAffiliationsInConference ( final Conversation conference , MucOptions . Affiliation before , MucOptions . Affiliation after ) {
List < Jid > jids = new ArrayList < > ( ) ;
for ( MucOptions . User user : conference . getMucOptions ( ) . getUsers ( ) ) {
if ( user . getAffiliation ( ) = = before & & user . getRealJid ( ) ! = null ) {
jids . add ( user . getRealJid ( ) ) ;
}
}
IqPacket request = this . mIqGenerator . changeAffiliation ( conference , jids , after . toString ( ) ) ;
sendIqPacket ( conference . getAccount ( ) , request , mDefaultIqHandler ) ;
}
public void changeRoleInConference ( final Conversation conference , final String nick , MucOptions . Role role ) {
IqPacket request = this . mIqGenerator . changeRole ( conference , nick , role . toString ( ) ) ;
Log . d ( Config . LOGTAG , request . toString ( ) ) ;
sendIqPacket ( conference . getAccount ( ) , request , ( account , packet ) - > {
if ( packet . getType ( ) ! = IqPacket . TYPE . RESULT ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " unable to change role of " + nick ) ;
}
} ) ;
}
public void destroyRoom ( final Conversation conversation , final OnRoomDestroy callback ) {
IqPacket request = new IqPacket ( IqPacket . TYPE . SET ) ;
request . setTo ( conversation . getJid ( ) . asBareJid ( ) ) ;
request . query ( " http://jabber.org/protocol/muc#owner " ) . addChild ( " destroy " ) ;
sendIqPacket ( conversation . getAccount ( ) , request , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
if ( callback ! = null ) {
callback . onRoomDestroySucceeded ( ) ;
}
} else if ( packet . getType ( ) = = IqPacket . TYPE . ERROR ) {
if ( callback ! = null ) {
callback . onRoomDestroyFailed ( ) ;
}
}
}
} ) ;
}
private void disconnect ( Account account , boolean force ) {
if ( ( account . getStatus ( ) = = Account . State . ONLINE )
| | ( account . getStatus ( ) = = Account . State . DISABLED ) ) {
final XmppConnection connection = account . getXmppConnection ( ) ;
if ( ! force ) {
List < Conversation > conversations = getConversations ( ) ;
for ( Conversation conversation : conversations ) {
if ( conversation . getAccount ( ) = = account ) {
if ( conversation . getMode ( ) = = Conversation . MODE_MULTI ) {
leaveMuc ( conversation , true ) ;
}
}
}
sendOfflinePresence ( account ) ;
}
connection . disconnect ( force ) ;
}
}
@Override
public IBinder onBind ( Intent intent ) {
return mBinder ;
}
public void updateMessage ( Message message ) {
updateMessage ( message , true ) ;
}
public void updateMessage ( Message message , boolean includeBody ) {
databaseBackend . updateMessage ( message , includeBody ) ;
updateConversationUi ( ) ;
}
2020-06-12 07:08:09 +00:00
public void createMessageAsync ( final Message message ) {
2020-06-12 18:06:49 +00:00
mDatabaseWriterExecutor . execute ( ( ) - > databaseBackend . createMessage ( message ) ) ;
2020-06-12 07:08:09 +00:00
}
2019-10-07 07:51:03 +00:00
public void updateMessage ( Message message , String uuid ) {
if ( ! databaseBackend . updateMessage ( message , uuid ) ) {
Log . e ( Config . LOGTAG , " error updated message in DB after edit " ) ;
}
updateConversationUi ( ) ;
}
protected void syncDirtyContacts ( Account account ) {
for ( Contact contact : account . getRoster ( ) . getContacts ( ) ) {
if ( contact . getOption ( Contact . Options . DIRTY_PUSH ) ) {
pushContactToServer ( contact ) ;
}
if ( contact . getOption ( Contact . Options . DIRTY_DELETE ) ) {
deleteContactOnServer ( contact ) ;
}
}
}
public void createContact ( Contact contact , boolean autoGrant ) {
if ( autoGrant ) {
contact . setOption ( Contact . Options . PREEMPTIVE_GRANT ) ;
contact . setOption ( Contact . Options . ASKING ) ;
}
pushContactToServer ( contact ) ;
}
public void pushContactToServer ( final Contact contact ) {
contact . resetOption ( Contact . Options . DIRTY_DELETE ) ;
contact . setOption ( Contact . Options . DIRTY_PUSH ) ;
final Account account = contact . getAccount ( ) ;
if ( account . getStatus ( ) = = Account . State . ONLINE ) {
final boolean ask = contact . getOption ( Contact . Options . ASKING ) ;
final boolean sendUpdates = contact
. getOption ( Contact . Options . PENDING_SUBSCRIPTION_REQUEST )
& & contact . getOption ( Contact . Options . PREEMPTIVE_GRANT ) ;
final IqPacket iq = new IqPacket ( IqPacket . TYPE . SET ) ;
iq . query ( Namespace . ROSTER ) . addChild ( contact . asElement ( ) ) ;
account . getXmppConnection ( ) . sendIqPacket ( iq , mDefaultIqHandler ) ;
if ( sendUpdates ) {
sendPresencePacket ( account , mPresenceGenerator . sendPresenceUpdatesTo ( contact ) ) ;
}
if ( ask ) {
sendPresencePacket ( account , mPresenceGenerator . requestPresenceUpdatesFrom ( contact ) ) ;
}
} else {
syncRoster ( contact . getAccount ( ) ) ;
}
}
public void publishMucAvatar ( final Conversation conversation , final Uri image , final OnAvatarPublication callback ) {
new Thread ( ( ) - > {
final Bitmap . CompressFormat format = Config . AVATAR_FORMAT ;
final int size = Config . AVATAR_SIZE ;
final Avatar avatar = getFileBackend ( ) . getPepAvatar ( image , size , format ) ;
if ( avatar ! = null ) {
if ( ! getFileBackend ( ) . save ( avatar ) ) {
callback . onAvatarPublicationFailed ( R . string . error_saving_avatar ) ;
return ;
}
avatar . owner = conversation . getJid ( ) . asBareJid ( ) ;
publishMucAvatar ( conversation , avatar , callback ) ;
} else {
callback . onAvatarPublicationFailed ( R . string . error_publish_avatar_converting ) ;
}
} ) . start ( ) ;
}
public void publishAvatar ( final Account account , final Uri image , final OnAvatarPublication callback ) {
new Thread ( ( ) - > {
final Bitmap . CompressFormat format = Config . AVATAR_FORMAT ;
final int size = Config . AVATAR_SIZE ;
final Avatar avatar = getFileBackend ( ) . getPepAvatar ( image , size , format ) ;
if ( avatar ! = null ) {
if ( ! getFileBackend ( ) . save ( avatar ) ) {
Log . d ( Config . LOGTAG , " unable to save vcard " ) ;
callback . onAvatarPublicationFailed ( R . string . error_saving_avatar ) ;
return ;
}
publishAvatar ( account , avatar , callback ) ;
} else {
callback . onAvatarPublicationFailed ( R . string . error_publish_avatar_converting ) ;
}
} ) . start ( ) ;
}
private void publishMucAvatar ( Conversation conversation , Avatar avatar , OnAvatarPublication callback ) {
final IqPacket retrieve = mIqGenerator . retrieveVcardAvatar ( avatar ) ;
sendIqPacket ( conversation . getAccount ( ) , retrieve , ( account , response ) - > {
boolean itemNotFound = response . getType ( ) = = IqPacket . TYPE . ERROR & & response . hasChild ( " error " ) & & response . findChild ( " error " ) . hasChild ( " item-not-found " ) ;
if ( response . getType ( ) = = IqPacket . TYPE . RESULT | | itemNotFound ) {
Element vcard = response . findChild ( " vCard " , " vcard-temp " ) ;
if ( vcard = = null ) {
vcard = new Element ( " vCard " , " vcard-temp " ) ;
}
Element photo = vcard . findChild ( " PHOTO " ) ;
if ( photo = = null ) {
photo = vcard . addChild ( " PHOTO " ) ;
}
photo . clearChildren ( ) ;
photo . addChild ( " TYPE " ) . setContent ( avatar . type ) ;
photo . addChild ( " BINVAL " ) . setContent ( avatar . image ) ;
IqPacket publication = new IqPacket ( IqPacket . TYPE . SET ) ;
publication . setTo ( conversation . getJid ( ) . asBareJid ( ) ) ;
publication . addChild ( vcard ) ;
sendIqPacket ( account , publication , ( a1 , publicationResponse ) - > {
if ( publicationResponse . getType ( ) = = IqPacket . TYPE . RESULT ) {
callback . onAvatarPublicationSucceeded ( ) ;
} else {
Log . d ( Config . LOGTAG , " failed to publish vcard " + publicationResponse . getError ( ) ) ;
callback . onAvatarPublicationFailed ( R . string . error_publish_avatar_server_reject ) ;
}
} ) ;
} else {
Log . d ( Config . LOGTAG , " failed to request vcard " + response . toString ( ) ) ;
callback . onAvatarPublicationFailed ( R . string . error_publish_avatar_no_server_support ) ;
}
} ) ;
}
public void publishAvatar ( Account account , final Avatar avatar , final OnAvatarPublication callback ) {
final Bundle options ;
if ( account . getXmppConnection ( ) . getFeatures ( ) . pepPublishOptions ( ) ) {
options = PublishOptions . openAccess ( ) ;
} else {
options = null ;
}
publishAvatar ( account , avatar , options , true , callback ) ;
}
public void publishAvatar ( Account account , final Avatar avatar , final Bundle options , final boolean retry , final OnAvatarPublication callback ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : publishing avatar. options= " + options ) ;
IqPacket packet = this . mIqGenerator . publishAvatar ( avatar , options ) ;
this . sendIqPacket ( account , packet , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket result ) {
if ( result . getType ( ) = = IqPacket . TYPE . RESULT ) {
publishAvatarMetadata ( account , avatar , options , true , callback ) ;
} else if ( retry & & PublishOptions . preconditionNotMet ( result ) ) {
pushNodeConfiguration ( account , " urn:xmpp:avatar:data " , options , new OnConfigurationPushed ( ) {
@Override
public void onPushSucceeded ( ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : changed node configuration for avatar node " ) ;
publishAvatar ( account , avatar , options , false , callback ) ;
}
@Override
public void onPushFailed ( ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : unable to change node configuration for avatar node " ) ;
publishAvatar ( account , avatar , null , false , callback ) ;
}
} ) ;
} else {
Element error = result . findChild ( " error " ) ;
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : server rejected avatar " + ( avatar . size / 1024 ) + " KiB " + ( error ! = null ? error . toString ( ) : " " ) ) ;
if ( callback ! = null ) {
callback . onAvatarPublicationFailed ( R . string . error_publish_avatar_server_reject ) ;
}
}
}
} ) ;
}
public void publishAvatarMetadata ( Account account , final Avatar avatar , final Bundle options , final boolean retry , final OnAvatarPublication callback ) {
final IqPacket packet = XmppConnectionService . this . mIqGenerator . publishAvatarMetadata ( avatar , options ) ;
sendIqPacket ( account , packet , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket result ) {
if ( result . getType ( ) = = IqPacket . TYPE . RESULT ) {
if ( account . setAvatar ( avatar . getFilename ( ) ) ) {
getAvatarService ( ) . clear ( account ) ;
databaseBackend . updateAccount ( account ) ;
notifyAccountAvatarHasChanged ( account ) ;
}
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : published avatar " + ( avatar . size / 1024 ) + " KiB " ) ;
if ( callback ! = null ) {
callback . onAvatarPublicationSucceeded ( ) ;
}
} else if ( retry & & PublishOptions . preconditionNotMet ( result ) ) {
pushNodeConfiguration ( account , " urn:xmpp:avatar:metadata " , options , new OnConfigurationPushed ( ) {
@Override
public void onPushSucceeded ( ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : changed node configuration for avatar meta data node " ) ;
publishAvatarMetadata ( account , avatar , options , false , callback ) ;
}
@Override
public void onPushFailed ( ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : unable to change node configuration for avatar meta data node " ) ;
publishAvatarMetadata ( account , avatar , null , false , callback ) ;
}
} ) ;
} else {
if ( callback ! = null ) {
callback . onAvatarPublicationFailed ( R . string . error_publish_avatar_server_reject ) ;
}
}
}
} ) ;
}
public void republishAvatarIfNeeded ( Account account ) {
if ( account . getAxolotlService ( ) . isPepBroken ( ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : skipping republication of avatar because pep is broken " ) ;
return ;
}
IqPacket packet = this . mIqGenerator . retrieveAvatarMetaData ( null ) ;
this . sendIqPacket ( account , packet , new OnIqPacketReceived ( ) {
private Avatar parseAvatar ( IqPacket packet ) {
Element pubsub = packet . findChild ( " pubsub " , " http://jabber.org/protocol/pubsub " ) ;
if ( pubsub ! = null ) {
Element items = pubsub . findChild ( " items " ) ;
if ( items ! = null ) {
return Avatar . parseMetadata ( items ) ;
}
}
return null ;
}
private boolean errorIsItemNotFound ( IqPacket packet ) {
Element error = packet . findChild ( " error " ) ;
return packet . getType ( ) = = IqPacket . TYPE . ERROR
& & error ! = null
& & error . hasChild ( " item-not-found " ) ;
}
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT | | errorIsItemNotFound ( packet ) ) {
Avatar serverAvatar = parseAvatar ( packet ) ;
if ( serverAvatar = = null & & account . getAvatar ( ) ! = null ) {
Avatar avatar = fileBackend . getStoredPepAvatar ( account . getAvatar ( ) ) ;
if ( avatar ! = null ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : avatar on server was null. republishing " ) ;
publishAvatar ( account , fileBackend . getStoredPepAvatar ( account . getAvatar ( ) ) , null ) ;
} else {
Log . e ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : error rereading avatar " ) ;
}
}
}
}
} ) ;
}
public void fetchAvatar ( Account account , Avatar avatar ) {
fetchAvatar ( account , avatar , null ) ;
}
public void fetchAvatar ( Account account , final Avatar avatar , final UiCallback < Avatar > callback ) {
final String KEY = generateFetchKey ( account , avatar ) ;
synchronized ( this . mInProgressAvatarFetches ) {
if ( mInProgressAvatarFetches . add ( KEY ) ) {
switch ( avatar . origin ) {
case PEP :
this . mInProgressAvatarFetches . add ( KEY ) ;
fetchAvatarPep ( account , avatar , callback ) ;
break ;
case VCARD :
this . mInProgressAvatarFetches . add ( KEY ) ;
fetchAvatarVcard ( account , avatar , callback ) ;
break ;
}
} else if ( avatar . origin = = Avatar . Origin . PEP ) {
mOmittedPepAvatarFetches . add ( KEY ) ;
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : already fetching " + avatar . origin + " avatar for " + avatar . owner ) ;
}
}
}
private void fetchAvatarPep ( Account account , final Avatar avatar , final UiCallback < Avatar > callback ) {
IqPacket packet = this . mIqGenerator . retrievePepAvatar ( avatar ) ;
sendIqPacket ( account , packet , ( a , result ) - > {
synchronized ( mInProgressAvatarFetches ) {
mInProgressAvatarFetches . remove ( generateFetchKey ( a , avatar ) ) ;
}
final String ERROR = a . getJid ( ) . asBareJid ( ) + " : fetching avatar for " + avatar . owner + " failed " ;
if ( result . getType ( ) = = IqPacket . TYPE . RESULT ) {
avatar . image = mIqParser . avatarData ( result ) ;
if ( avatar . image ! = null ) {
if ( getFileBackend ( ) . save ( avatar ) ) {
if ( a . getJid ( ) . asBareJid ( ) . equals ( avatar . owner ) ) {
if ( a . setAvatar ( avatar . getFilename ( ) ) ) {
databaseBackend . updateAccount ( a ) ;
}
getAvatarService ( ) . clear ( a ) ;
updateConversationUi ( ) ;
updateAccountUi ( ) ;
} else {
Contact contact = a . getRoster ( ) . getContact ( avatar . owner ) ;
if ( contact . setAvatar ( avatar ) ) {
syncRoster ( account ) ;
getAvatarService ( ) . clear ( contact ) ;
updateConversationUi ( ) ;
updateRosterUi ( ) ;
}
}
if ( callback ! = null ) {
callback . success ( avatar ) ;
}
Log . d ( Config . LOGTAG , a . getJid ( ) . asBareJid ( )
+ " : successfully fetched pep avatar for " + avatar . owner ) ;
return ;
}
} else {
Log . d ( Config . LOGTAG , ERROR + " (parsing error) " ) ;
}
} else {
Element error = result . findChild ( " error " ) ;
if ( error = = null ) {
Log . d ( Config . LOGTAG , ERROR + " (server error) " ) ;
} else {
Log . d ( Config . LOGTAG , ERROR + error . toString ( ) ) ;
}
}
if ( callback ! = null ) {
callback . error ( 0 , null ) ;
}
} ) ;
}
private void fetchAvatarVcard ( final Account account , final Avatar avatar , final UiCallback < Avatar > callback ) {
IqPacket packet = this . mIqGenerator . retrieveVcardAvatar ( avatar ) ;
this . sendIqPacket ( account , packet , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
final boolean previouslyOmittedPepFetch ;
synchronized ( mInProgressAvatarFetches ) {
final String KEY = generateFetchKey ( account , avatar ) ;
mInProgressAvatarFetches . remove ( KEY ) ;
previouslyOmittedPepFetch = mOmittedPepAvatarFetches . remove ( KEY ) ;
}
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
Element vCard = packet . findChild ( " vCard " , " vcard-temp " ) ;
Element photo = vCard ! = null ? vCard . findChild ( " PHOTO " ) : null ;
String image = photo ! = null ? photo . findChildContent ( " BINVAL " ) : null ;
if ( image ! = null ) {
avatar . image = image ;
if ( getFileBackend ( ) . save ( avatar ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( )
+ " : successfully fetched vCard avatar for " + avatar . owner + " omittedPep= " + previouslyOmittedPepFetch ) ;
if ( avatar . owner . isBareJid ( ) ) {
if ( account . getJid ( ) . asBareJid ( ) . equals ( avatar . owner ) & & account . getAvatar ( ) = = null ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : had no avatar. replacing with vcard " ) ;
account . setAvatar ( avatar . getFilename ( ) ) ;
databaseBackend . updateAccount ( account ) ;
getAvatarService ( ) . clear ( account ) ;
updateAccountUi ( ) ;
} else {
Contact contact = account . getRoster ( ) . getContact ( avatar . owner ) ;
if ( contact . setAvatar ( avatar , previouslyOmittedPepFetch ) ) {
syncRoster ( account ) ;
getAvatarService ( ) . clear ( contact ) ;
updateRosterUi ( ) ;
}
}
updateConversationUi ( ) ;
} else {
Conversation conversation = find ( account , avatar . owner . asBareJid ( ) ) ;
if ( conversation ! = null & & conversation . getMode ( ) = = Conversation . MODE_MULTI ) {
MucOptions . User user = conversation . getMucOptions ( ) . findUserByFullJid ( avatar . owner ) ;
if ( user ! = null ) {
if ( user . setAvatar ( avatar ) ) {
getAvatarService ( ) . clear ( user ) ;
updateConversationUi ( ) ;
updateMucRosterUi ( ) ;
}
if ( user . getRealJid ( ) ! = null ) {
Contact contact = account . getRoster ( ) . getContact ( user . getRealJid ( ) ) ;
if ( contact . setAvatar ( avatar ) ) {
syncRoster ( account ) ;
getAvatarService ( ) . clear ( contact ) ;
updateRosterUi ( ) ;
}
}
}
}
}
}
}
}
}
} ) ;
}
public void checkForAvatar ( Account account , final UiCallback < Avatar > callback ) {
IqPacket packet = this . mIqGenerator . retrieveAvatarMetaData ( null ) ;
this . sendIqPacket ( account , packet , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
Element pubsub = packet . findChild ( " pubsub " , " http://jabber.org/protocol/pubsub " ) ;
if ( pubsub ! = null ) {
Element items = pubsub . findChild ( " items " ) ;
if ( items ! = null ) {
Avatar avatar = Avatar . parseMetadata ( items ) ;
if ( avatar ! = null ) {
avatar . owner = account . getJid ( ) . asBareJid ( ) ;
if ( fileBackend . isAvatarCached ( avatar ) ) {
if ( account . setAvatar ( avatar . getFilename ( ) ) ) {
databaseBackend . updateAccount ( account ) ;
}
getAvatarService ( ) . clear ( account ) ;
callback . success ( avatar ) ;
} else {
fetchAvatarPep ( account , avatar , callback ) ;
}
return ;
}
}
}
}
callback . error ( 0 , null ) ;
}
} ) ;
}
public void notifyAccountAvatarHasChanged ( final Account account ) {
final XmppConnection connection = account . getXmppConnection ( ) ;
if ( connection ! = null & & connection . getFeatures ( ) . bookmarksConversion ( ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : avatar changed. resending presence to online group chats " ) ;
for ( Conversation conversation : conversations ) {
if ( conversation . getAccount ( ) = = account & & conversation . getMode ( ) = = Conversational . MODE_MULTI ) {
final MucOptions mucOptions = conversation . getMucOptions ( ) ;
if ( mucOptions . online ( ) ) {
PresencePacket packet = mPresenceGenerator . selfPresence ( account , Presence . Status . ONLINE , mucOptions . nonanonymous ( ) ) ;
packet . setTo ( mucOptions . getSelf ( ) . getFullJid ( ) ) ;
connection . sendPresencePacket ( packet ) ;
}
}
}
}
}
public void deleteContactOnServer ( Contact contact ) {
contact . resetOption ( Contact . Options . PREEMPTIVE_GRANT ) ;
contact . resetOption ( Contact . Options . DIRTY_PUSH ) ;
contact . setOption ( Contact . Options . DIRTY_DELETE ) ;
Account account = contact . getAccount ( ) ;
if ( account . getStatus ( ) = = Account . State . ONLINE ) {
IqPacket iq = new IqPacket ( IqPacket . TYPE . SET ) ;
Element item = iq . query ( Namespace . ROSTER ) . addChild ( " item " ) ;
2020-05-15 15:06:16 +00:00
item . setAttribute ( " jid " , contact . getJid ( ) ) ;
2019-10-07 07:51:03 +00:00
item . setAttribute ( " subscription " , " remove " ) ;
account . getXmppConnection ( ) . sendIqPacket ( iq , mDefaultIqHandler ) ;
}
}
public void updateConversation ( final Conversation conversation ) {
mDatabaseWriterExecutor . execute ( ( ) - > databaseBackend . updateConversation ( conversation ) ) ;
}
private void reconnectAccount ( final Account account , final boolean force , final boolean interactive ) {
synchronized ( account ) {
XmppConnection connection = account . getXmppConnection ( ) ;
if ( connection = = null ) {
connection = createConnection ( account ) ;
account . setXmppConnection ( connection ) ;
}
boolean hasInternet = hasInternetConnection ( ) ;
if ( account . isEnabled ( ) & & hasInternet ) {
if ( ! force ) {
disconnect ( account , false ) ;
}
Thread thread = new Thread ( connection ) ;
connection . setInteractive ( interactive ) ;
connection . prepareNewConnection ( ) ;
connection . interrupt ( ) ;
thread . start ( ) ;
scheduleWakeUpCall ( Config . CONNECT_DISCO_TIMEOUT , account . getUuid ( ) . hashCode ( ) ) ;
} else {
disconnect ( account , force | | account . getTrueStatus ( ) . isError ( ) | | ! hasInternet ) ;
account . getRoster ( ) . clearPresences ( ) ;
connection . resetEverything ( ) ;
final AxolotlService axolotlService = account . getAxolotlService ( ) ;
if ( axolotlService ! = null ) {
axolotlService . resetBrokenness ( ) ;
}
if ( ! hasInternet ) {
account . setStatus ( Account . State . NO_INTERNET ) ;
}
}
}
}
public void reconnectAccountInBackground ( final Account account ) {
new Thread ( ( ) - > reconnectAccount ( account , false , true ) ) . start ( ) ;
}
public void invite ( Conversation conversation , Jid contact ) {
Log . d ( Config . LOGTAG , conversation . getAccount ( ) . getJid ( ) . asBareJid ( ) + " : inviting " + contact + " to " + conversation . getJid ( ) . asBareJid ( ) ) ;
MessagePacket packet = mMessageGenerator . invite ( conversation , contact ) ;
sendMessagePacket ( conversation . getAccount ( ) , packet ) ;
}
public void directInvite ( Conversation conversation , Jid jid ) {
MessagePacket packet = mMessageGenerator . directInvite ( conversation , jid ) ;
sendMessagePacket ( conversation . getAccount ( ) , packet ) ;
}
public void resetSendingToWaiting ( Account account ) {
for ( Conversation conversation : getConversations ( ) ) {
if ( conversation . getAccount ( ) = = account ) {
conversation . findUnsentTextMessages ( message - > markMessage ( message , Message . STATUS_WAITING ) ) ;
}
}
}
public Message markMessage ( final Account account , final Jid recipient , final String uuid , final int status ) {
return markMessage ( account , recipient , uuid , status , null ) ;
}
public Message markMessage ( final Account account , final Jid recipient , final String uuid , final int status , String errorMessage ) {
if ( uuid = = null ) {
return null ;
}
for ( Conversation conversation : getConversations ( ) ) {
if ( conversation . getJid ( ) . asBareJid ( ) . equals ( recipient ) & & conversation . getAccount ( ) = = account ) {
final Message message = conversation . findSentMessageWithUuidOrRemoteId ( uuid ) ;
if ( message ! = null ) {
markMessage ( message , status , errorMessage ) ;
}
return message ;
}
}
return null ;
}
public boolean markMessage ( Conversation conversation , String uuid , int status , String serverMessageId ) {
if ( uuid = = null ) {
return false ;
} else {
Message message = conversation . findSentMessageWithUuid ( uuid ) ;
if ( message ! = null ) {
if ( message . getServerMsgId ( ) = = null ) {
message . setServerMsgId ( serverMessageId ) ;
}
markMessage ( message , status ) ;
return true ;
} else {
return false ;
}
}
}
public void markMessage ( Message message , int status ) {
markMessage ( message , status , null ) ;
}
public void markMessage ( Message message , int status , String errorMessage ) {
final int oldStatus = message . getStatus ( ) ;
if ( status = = Message . STATUS_SEND_FAILED & & ( oldStatus = = Message . STATUS_SEND_RECEIVED | | oldStatus = = Message . STATUS_SEND_DISPLAYED ) ) {
return ;
}
if ( status = = Message . STATUS_SEND_RECEIVED & & oldStatus = = Message . STATUS_SEND_DISPLAYED ) {
return ;
}
message . setErrorMessage ( errorMessage ) ;
message . setStatus ( status ) ;
databaseBackend . updateMessage ( message , false ) ;
updateConversationUi ( ) ;
}
private SharedPreferences getPreferences ( ) {
return PreferenceManager . getDefaultSharedPreferences ( getApplicationContext ( ) ) ;
}
public long getAutomaticMessageDeletionDate ( ) {
final long timeout = getLongPreference ( SettingsActivity . AUTOMATIC_MESSAGE_DELETION , R . integer . automatic_message_deletion ) ;
return timeout = = 0 ? timeout : ( System . currentTimeMillis ( ) - ( timeout * 1000 ) ) ;
}
public long getLongPreference ( String name , @IntegerRes int res ) {
long defaultValue = getResources ( ) . getInteger ( res ) ;
try {
return Long . parseLong ( getPreferences ( ) . getString ( name , String . valueOf ( defaultValue ) ) ) ;
} catch ( NumberFormatException e ) {
return defaultValue ;
}
}
public boolean getBooleanPreference ( String name , @BoolRes int res ) {
return getPreferences ( ) . getBoolean ( name , getResources ( ) . getBoolean ( res ) ) ;
}
public boolean confirmMessages ( ) {
return getBooleanPreference ( " confirm_messages " , R . bool . confirm_messages ) ;
}
public boolean allowMessageCorrection ( ) {
return getBooleanPreference ( " allow_message_correction " , R . bool . allow_message_correction ) ;
}
public boolean sendChatStates ( ) {
return getBooleanPreference ( " chat_states " , R . bool . chat_states ) ;
}
private boolean synchronizeWithBookmarks ( ) {
return getBooleanPreference ( " autojoin " , R . bool . autojoin ) ;
}
public boolean useTorToConnect ( ) {
return QuickConversationsService . isConversations ( ) & & getBooleanPreference ( " use_tor " , R . bool . use_tor ) ;
}
public boolean showExtendedConnectionOptions ( ) {
return QuickConversationsService . isConversations ( ) & & getBooleanPreference ( " show_connection_options " , R . bool . show_connection_options ) ;
}
public boolean broadcastLastActivity ( ) {
return getBooleanPreference ( SettingsActivity . BROADCAST_LAST_ACTIVITY , R . bool . last_activity ) ;
}
public int unreadCount ( ) {
int count = 0 ;
for ( Conversation conversation : getConversations ( ) ) {
count + = conversation . unreadCount ( ) ;
}
return count ;
}
private < T > List < T > threadSafeList ( Set < T > set ) {
synchronized ( LISTENER_LOCK ) {
return set . size ( ) = = 0 ? Collections . emptyList ( ) : new ArrayList < > ( set ) ;
}
}
public void showErrorToastInUi ( int resId ) {
for ( OnShowErrorToast listener : threadSafeList ( this . mOnShowErrorToasts ) ) {
listener . onShowErrorToast ( resId ) ;
}
}
public void updateConversationUi ( ) {
for ( OnConversationUpdate listener : threadSafeList ( this . mOnConversationUpdates ) ) {
listener . onConversationUpdate ( ) ;
}
}
2020-04-08 07:42:06 +00:00
public void notifyJingleRtpConnectionUpdate ( final Account account , final Jid with , final String sessionId , final RtpEndUserState state ) {
2020-04-10 13:19:56 +00:00
for ( OnJingleRtpConnectionUpdate listener : threadSafeList ( this . onJingleRtpConnectionUpdate ) ) {
2020-04-08 07:42:06 +00:00
listener . onJingleRtpConnectionUpdate ( account , with , sessionId , state ) ;
2020-04-07 11:15:24 +00:00
}
}
2020-04-13 10:02:34 +00:00
public void notifyJingleRtpConnectionUpdate ( AppRTCAudioManager . AudioDevice selectedAudioDevice , Set < AppRTCAudioManager . AudioDevice > availableAudioDevices ) {
for ( OnJingleRtpConnectionUpdate listener : threadSafeList ( this . onJingleRtpConnectionUpdate ) ) {
listener . onAudioDeviceChanged ( selectedAudioDevice , availableAudioDevices ) ;
}
}
2019-10-07 07:51:03 +00:00
public void updateAccountUi ( ) {
for ( OnAccountUpdate listener : threadSafeList ( this . mOnAccountUpdates ) ) {
listener . onAccountUpdate ( ) ;
}
}
public void updateRosterUi ( ) {
for ( OnRosterUpdate listener : threadSafeList ( this . mOnRosterUpdates ) ) {
listener . onRosterUpdate ( ) ;
}
}
public boolean displayCaptchaRequest ( Account account , String id , Data data , Bitmap captcha ) {
if ( mOnCaptchaRequested . size ( ) > 0 ) {
DisplayMetrics metrics = getApplicationContext ( ) . getResources ( ) . getDisplayMetrics ( ) ;
Bitmap scaled = Bitmap . createScaledBitmap ( captcha , ( int ) ( captcha . getWidth ( ) * metrics . scaledDensity ) ,
( int ) ( captcha . getHeight ( ) * metrics . scaledDensity ) , false ) ;
for ( OnCaptchaRequested listener : threadSafeList ( this . mOnCaptchaRequested ) ) {
listener . onCaptchaRequested ( account , id , data , scaled ) ;
}
return true ;
}
return false ;
}
public void updateBlocklistUi ( final OnUpdateBlocklist . Status status ) {
for ( OnUpdateBlocklist listener : threadSafeList ( this . mOnUpdateBlocklist ) ) {
listener . OnUpdateBlocklist ( status ) ;
}
}
public void updateMucRosterUi ( ) {
for ( OnMucRosterUpdate listener : threadSafeList ( this . mOnMucRosterUpdate ) ) {
listener . onMucRosterUpdate ( ) ;
}
}
public void keyStatusUpdated ( AxolotlService . FetchStatus report ) {
for ( OnKeyStatusUpdated listener : threadSafeList ( this . mOnKeyStatusUpdated ) ) {
listener . onKeyStatusUpdated ( report ) ;
}
}
2020-04-07 11:15:24 +00:00
public Account findAccountByJid ( final Jid jid ) {
for ( final Account account : this . accounts ) {
if ( account . getJid ( ) . asBareJid ( ) . equals ( jid . asBareJid ( ) ) ) {
2019-10-07 07:51:03 +00:00
return account ;
}
}
return null ;
}
public Account findAccountByUuid ( final String uuid ) {
for ( Account account : this . accounts ) {
if ( account . getUuid ( ) . equals ( uuid ) ) {
return account ;
}
}
return null ;
}
public Conversation findConversationByUuid ( String uuid ) {
for ( Conversation conversation : getConversations ( ) ) {
if ( conversation . getUuid ( ) . equals ( uuid ) ) {
return conversation ;
}
}
return null ;
}
public Conversation findUniqueConversationByJid ( XmppUri xmppUri ) {
List < Conversation > findings = new ArrayList < > ( ) ;
for ( Conversation c : getConversations ( ) ) {
if ( c . getAccount ( ) . isEnabled ( ) & & c . getJid ( ) . asBareJid ( ) . equals ( xmppUri . getJid ( ) ) & & ( ( c . getMode ( ) = = Conversational . MODE_MULTI ) = = xmppUri . isAction ( XmppUri . ACTION_JOIN ) ) ) {
findings . add ( c ) ;
}
}
return findings . size ( ) = = 1 ? findings . get ( 0 ) : null ;
}
public boolean markRead ( final Conversation conversation , boolean dismiss ) {
return markRead ( conversation , null , dismiss ) . size ( ) > 0 ;
}
public void markRead ( final Conversation conversation ) {
markRead ( conversation , null , true ) ;
}
public List < Message > markRead ( final Conversation conversation , String upToUuid , boolean dismiss ) {
if ( dismiss ) {
mNotificationService . clear ( conversation ) ;
}
final List < Message > readMessages = conversation . markRead ( upToUuid ) ;
if ( readMessages . size ( ) > 0 ) {
Runnable runnable = ( ) - > {
for ( Message message : readMessages ) {
databaseBackend . updateMessage ( message , false ) ;
}
} ;
mDatabaseWriterExecutor . execute ( runnable ) ;
2020-04-28 06:25:21 +00:00
updateConversationUi ( ) ;
2019-10-07 07:51:03 +00:00
updateUnreadCountBadge ( ) ;
return readMessages ;
} else {
return readMessages ;
}
}
public synchronized void updateUnreadCountBadge ( ) {
int count = unreadCount ( ) ;
if ( unreadCount ! = count ) {
Log . d ( Config . LOGTAG , " update unread count to " + count ) ;
if ( count > 0 ) {
ShortcutBadger . applyCount ( getApplicationContext ( ) , count ) ;
} else {
ShortcutBadger . removeCount ( getApplicationContext ( ) ) ;
}
unreadCount = count ;
}
}
public void sendReadMarker ( final Conversation conversation , String upToUuid ) {
final boolean isPrivateAndNonAnonymousMuc = conversation . getMode ( ) = = Conversation . MODE_MULTI & & conversation . isPrivateAndNonAnonymous ( ) ;
final List < Message > readMessages = this . markRead ( conversation , upToUuid , true ) ;
if ( readMessages . size ( ) > 0 ) {
updateConversationUi ( ) ;
}
final Message markable = Conversation . getLatestMarkableMessage ( readMessages , isPrivateAndNonAnonymousMuc ) ;
if ( confirmMessages ( )
& & markable ! = null
& & ( markable . trusted ( ) | | isPrivateAndNonAnonymousMuc )
& & markable . getRemoteMsgId ( ) ! = null ) {
Log . d ( Config . LOGTAG , conversation . getAccount ( ) . getJid ( ) . asBareJid ( ) + " : sending read marker to " + markable . getCounterpart ( ) . toString ( ) ) ;
2020-04-28 06:25:21 +00:00
final Account account = conversation . getAccount ( ) ;
final MessagePacket packet = mMessageGenerator . confirm ( markable ) ;
this . sendMessagePacket ( account , packet ) ;
2019-10-07 07:51:03 +00:00
}
}
public SecureRandom getRNG ( ) {
return this . mRandom ;
}
public MemorizingTrustManager getMemorizingTrustManager ( ) {
return this . mMemorizingTrustManager ;
}
public void setMemorizingTrustManager ( MemorizingTrustManager trustManager ) {
this . mMemorizingTrustManager = trustManager ;
}
public void updateMemorizingTrustmanager ( ) {
final MemorizingTrustManager tm ;
final boolean dontTrustSystemCAs = getBooleanPreference ( " dont_trust_system_cas " , R . bool . dont_trust_system_cas ) ;
if ( dontTrustSystemCAs ) {
tm = new MemorizingTrustManager ( getApplicationContext ( ) , null ) ;
} else {
tm = new MemorizingTrustManager ( getApplicationContext ( ) ) ;
}
setMemorizingTrustManager ( tm ) ;
}
public LruCache < String , Bitmap > getBitmapCache ( ) {
return this . mBitmapCache ;
}
public Collection < String > getKnownHosts ( ) {
final Set < String > hosts = new HashSet < > ( ) ;
for ( final Account account : getAccounts ( ) ) {
hosts . add ( account . getServer ( ) ) ;
for ( final Contact contact : account . getRoster ( ) . getContacts ( ) ) {
if ( contact . showInRoster ( ) ) {
final String server = contact . getServer ( ) ;
if ( server ! = null ) {
hosts . add ( server ) ;
}
}
}
}
if ( Config . QUICKSY_DOMAIN ! = null ) {
2020-06-13 06:26:32 +00:00
hosts . remove ( Config . QUICKSY_DOMAIN . toEscapedString ( ) ) ; //we only want to show this when we type a e164 number
2019-10-07 07:51:03 +00:00
}
if ( Config . DOMAIN_LOCK ! = null ) {
hosts . add ( Config . DOMAIN_LOCK ) ;
}
if ( Config . MAGIC_CREATE_DOMAIN ! = null ) {
hosts . add ( Config . MAGIC_CREATE_DOMAIN ) ;
}
return hosts ;
}
public Collection < String > getKnownConferenceHosts ( ) {
final Set < String > mucServers = new HashSet < > ( ) ;
for ( final Account account : accounts ) {
if ( account . getXmppConnection ( ) ! = null ) {
mucServers . addAll ( account . getXmppConnection ( ) . getMucServers ( ) ) ;
for ( Bookmark bookmark : account . getBookmarks ( ) ) {
final Jid jid = bookmark . getJid ( ) ;
2020-05-17 08:24:46 +00:00
final String s = jid = = null ? null : jid . getDomain ( ) . toEscapedString ( ) ;
2019-10-07 07:51:03 +00:00
if ( s ! = null ) {
mucServers . add ( s ) ;
}
}
}
}
return mucServers ;
}
public void sendMessagePacket ( Account account , MessagePacket packet ) {
XmppConnection connection = account . getXmppConnection ( ) ;
if ( connection ! = null ) {
connection . sendMessagePacket ( packet ) ;
}
}
public void sendPresencePacket ( Account account , PresencePacket packet ) {
XmppConnection connection = account . getXmppConnection ( ) ;
if ( connection ! = null ) {
connection . sendPresencePacket ( packet ) ;
}
}
public void sendCreateAccountWithCaptchaPacket ( Account account , String id , Data data ) {
final XmppConnection connection = account . getXmppConnection ( ) ;
if ( connection ! = null ) {
IqPacket request = mIqGenerator . generateCreateAccountWithCaptcha ( account , id , data ) ;
connection . sendUnmodifiedIqPacket ( request , connection . registrationResponseListener , true ) ;
}
}
public void sendIqPacket ( final Account account , final IqPacket packet , final OnIqPacketReceived callback ) {
final XmppConnection connection = account . getXmppConnection ( ) ;
if ( connection ! = null ) {
connection . sendIqPacket ( packet , callback ) ;
} else if ( callback ! = null ) {
callback . onIqPacketReceived ( account , new IqPacket ( IqPacket . TYPE . TIMEOUT ) ) ;
}
}
public void sendPresence ( final Account account ) {
sendPresence ( account , checkListeners ( ) & & broadcastLastActivity ( ) ) ;
}
private void sendPresence ( final Account account , final boolean includeIdleTimestamp ) {
Presence . Status status ;
if ( manuallyChangePresence ( ) ) {
status = account . getPresenceStatus ( ) ;
} else {
status = getTargetPresence ( ) ;
}
final PresencePacket 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 ( ) ;
}
}
public void refreshAllPresences ( ) {
boolean includeIdleTimestamp = checkListeners ( ) & & broadcastLastActivity ( ) ;
for ( Account account : getAccounts ( ) ) {
if ( account . isEnabled ( ) ) {
sendPresence ( account , includeIdleTimestamp ) ;
}
}
}
private void refreshAllFcmTokens ( ) {
for ( Account account : getAccounts ( ) ) {
if ( account . isOnlineAndConnected ( ) & & mPushManagementService . available ( account ) ) {
mPushManagementService . registerPushTokenOnServer ( account ) ;
//TODO renew mucs
}
}
}
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 ;
}
public PresenceGenerator getPresenceGenerator ( ) {
return this . mPresenceGenerator ;
}
public IqGenerator getIqGenerator ( ) {
return this . mIqGenerator ;
}
public IqParser getIqParser ( ) {
return this . mIqParser ;
}
public JingleConnectionManager getJingleConnectionManager ( ) {
return this . mJingleConnectionManager ;
}
public MessageArchiveService getMessageArchiveService ( ) {
return this . mMessageArchiveService ;
}
public QuickConversationsService getQuickConversationsService ( ) {
return this . mQuickConversationsService ;
}
public List < Contact > findContacts ( Jid jid , String accountJid ) {
ArrayList < Contact > contacts = new ArrayList < > ( ) ;
for ( Account account : getAccounts ( ) ) {
if ( ( account . isEnabled ( ) | | accountJid ! = null )
& & ( accountJid = = null | | accountJid . equals ( account . getJid ( ) . asBareJid ( ) . toString ( ) ) ) ) {
Contact contact = account . getRoster ( ) . getContactFromContactList ( jid ) ;
if ( contact ! = null ) {
contacts . add ( contact ) ;
}
}
}
return contacts ;
}
2018-02-10 15:24:55 +00:00
2019-10-07 07:51:03 +00:00
public Conversation findFirstMuc ( Jid jid ) {
for ( Conversation conversation : getConversations ( ) ) {
if ( conversation . getAccount ( ) . isEnabled ( ) & & conversation . getJid ( ) . asBareJid ( ) . equals ( jid . asBareJid ( ) ) & & conversation . getMode ( ) = = Conversation . MODE_MULTI ) {
return conversation ;
2019-08-19 11:55:52 +00:00
}
2018-11-10 16:33:24 +00:00
}
2019-10-07 07:51:03 +00:00
return null ;
}
2014-11-20 17:20:42 +00:00
2019-10-07 07:51:03 +00:00
public NotificationService getNotificationService ( ) {
return this . mNotificationService ;
}
public HttpConnectionManager getHttpConnectionManager ( ) {
return this . mHttpConnectionManager ;
}
public void resendFailedMessages ( final Message message ) {
final Collection < Message > messages = new ArrayList < > ( ) ;
Message current = message ;
while ( current . getStatus ( ) = = Message . STATUS_SEND_FAILED ) {
messages . add ( current ) ;
if ( current . mergeable ( current . next ( ) ) ) {
current = current . next ( ) ;
} else {
break ;
}
2019-06-30 19:57:37 +00:00
}
2019-10-07 07:51:03 +00:00
for ( final Message msg : messages ) {
msg . setTime ( System . currentTimeMillis ( ) ) ;
markMessage ( msg , Message . STATUS_WAITING ) ;
this . resendMessage ( msg , false ) ;
2019-06-30 19:57:37 +00:00
}
2019-10-07 07:51:03 +00:00
if ( message . getConversation ( ) instanceof Conversation ) {
( ( Conversation ) message . getConversation ( ) ) . sort ( ) ;
}
updateConversationUi ( ) ;
}
public void clearConversationHistory ( final Conversation conversation ) {
final long clearDate ;
final String reference ;
if ( conversation . countMessages ( ) > 0 ) {
Message latestMessage = conversation . getLatestMessage ( ) ;
clearDate = latestMessage . getTimeSent ( ) + 1000 ;
reference = latestMessage . getServerMsgId ( ) ;
} else {
clearDate = System . currentTimeMillis ( ) ;
reference = null ;
}
conversation . clearMessages ( ) ;
conversation . setHasMessagesLeftOnServer ( false ) ; //avoid messages getting loaded through mam
conversation . setLastClearHistory ( clearDate , reference ) ;
Runnable runnable = ( ) - > {
databaseBackend . deleteMessagesInConversation ( conversation ) ;
databaseBackend . updateConversation ( conversation ) ;
} ;
mDatabaseWriterExecutor . execute ( runnable ) ;
}
public boolean sendBlockRequest ( final Blockable blockable , boolean reportSpam ) {
if ( blockable ! = null & & blockable . getBlockedJid ( ) ! = null ) {
final Jid jid = blockable . getBlockedJid ( ) ;
this . sendIqPacket ( blockable . getAccount ( ) , getIqGenerator ( ) . generateSetBlockRequest ( jid , reportSpam ) , ( a , response ) - > {
if ( response . getType ( ) = = IqPacket . TYPE . RESULT ) {
a . getBlocklist ( ) . add ( jid ) ;
updateBlocklistUi ( OnUpdateBlocklist . Status . BLOCKED ) ;
}
} ) ;
if ( blockable . getBlockedJid ( ) . isFullJid ( ) ) {
return false ;
} else if ( removeBlockedConversations ( blockable . getAccount ( ) , jid ) ) {
updateConversationUi ( ) ;
return true ;
} else {
return false ;
2019-02-06 13:45:51 +00:00
}
2019-10-07 07:51:03 +00:00
} else {
return false ;
}
}
public boolean removeBlockedConversations ( final Account account , final Jid blockedJid ) {
boolean removed = false ;
synchronized ( this . conversations ) {
boolean domainJid = blockedJid . getLocal ( ) = = null ;
for ( Conversation conversation : this . conversations ) {
boolean jidMatches = ( domainJid & & blockedJid . getDomain ( ) . equals ( conversation . getJid ( ) . getDomain ( ) ) )
| | blockedJid . equals ( conversation . getJid ( ) . asBareJid ( ) ) ;
if ( conversation . getAccount ( ) = = account
& & conversation . getMode ( ) = = Conversation . MODE_SINGLE
& & jidMatches ) {
this . conversations . remove ( conversation ) ;
markRead ( conversation ) ;
conversation . setStatus ( Conversation . STATUS_ARCHIVED ) ;
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : archiving conversation " + conversation . getJid ( ) . asBareJid ( ) + " because jid was blocked " ) ;
updateConversation ( conversation ) ;
removed = true ;
2019-02-06 13:45:51 +00:00
}
2019-10-07 07:51:03 +00:00
}
}
return removed ;
}
2019-02-06 13:45:51 +00:00
2019-10-07 07:51:03 +00:00
public void sendUnblockRequest ( final Blockable blockable ) {
if ( blockable ! = null & & blockable . getJid ( ) ! = null ) {
final Jid jid = blockable . getBlockedJid ( ) ;
this . sendIqPacket ( blockable . getAccount ( ) , getIqGenerator ( ) . generateSetUnblockRequest ( jid ) , new OnIqPacketReceived ( ) {
2019-02-06 13:45:51 +00:00
@Override
2019-10-07 07:51:03 +00:00
public void onIqPacketReceived ( final Account account , final IqPacket packet ) {
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT ) {
account . getBlocklist ( ) . remove ( jid ) ;
updateBlocklistUi ( OnUpdateBlocklist . Status . UNBLOCKED ) ;
2019-02-06 13:45:51 +00:00
}
}
} ) ;
2019-10-07 07:51:03 +00:00
}
2019-02-06 13:45:51 +00:00
}
2019-10-07 07:51:03 +00:00
public void publishDisplayName ( Account account ) {
String displayName = account . getDisplayName ( ) ;
final IqPacket request ;
if ( TextUtils . isEmpty ( displayName ) ) {
request = mIqGenerator . deleteNode ( Namespace . NICK ) ;
} else {
request = mIqGenerator . publishNick ( displayName ) ;
}
mAvatarService . clear ( account ) ;
sendIqPacket ( account , request , ( account1 , packet ) - > {
if ( packet . getType ( ) = = IqPacket . TYPE . ERROR ) {
Log . d ( Config . LOGTAG , account1 . getJid ( ) . asBareJid ( ) + " : unable to modify nick name " + packet . toString ( ) ) ;
}
} ) ;
}
2018-06-24 14:17:20 +00:00
2019-10-07 07:51:03 +00:00
public ServiceDiscoveryResult getCachedServiceDiscoveryResult ( Pair < String , String > key ) {
ServiceDiscoveryResult result = discoCache . get ( key ) ;
if ( result ! = null ) {
return result ;
} else {
result = databaseBackend . findDiscoveryResult ( key . first , key . second ) ;
if ( result ! = null ) {
discoCache . put ( key , result ) ;
}
return result ;
}
}
2018-06-24 14:17:20 +00:00
2019-10-07 07:51:03 +00:00
public void fetchCaps ( Account account , final Jid jid , final Presence presence ) {
final Pair < String , String > key = new Pair < > ( presence . getHash ( ) , presence . getVer ( ) ) ;
2020-06-12 18:06:49 +00:00
final ServiceDiscoveryResult disco = getCachedServiceDiscoveryResult ( key ) ;
2019-10-07 07:51:03 +00:00
if ( disco ! = null ) {
presence . setServiceDiscoveryResult ( disco ) ;
} else {
2020-06-12 18:06:49 +00:00
if ( account . inProgressDiscoFetches . contains ( key ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : skipping duplicate disco request for " + key . second + " to " + jid ) ;
return ;
}
account . inProgressDiscoFetches . add ( key ) ;
final IqPacket request = new IqPacket ( IqPacket . TYPE . GET ) ;
request . setTo ( jid ) ;
final String node = presence . getNode ( ) ;
final String ver = presence . getVer ( ) ;
final Element query = request . query ( Namespace . DISCO_INFO ) ;
if ( node ! = null & & ver ! = null ) {
query . setAttribute ( " node " , node + " # " + ver ) ;
2019-01-28 09:52:35 +00:00
}
2020-06-12 18:06:49 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : making disco request for " + key . second + " to " + jid ) ;
sendIqPacket ( account , request , ( a , response ) - > {
if ( response . getType ( ) = = IqPacket . TYPE . RESULT ) {
ServiceDiscoveryResult discoveryResult = new ServiceDiscoveryResult ( response ) ;
if ( presence . getVer ( ) . equals ( discoveryResult . getVer ( ) ) ) {
databaseBackend . insertDiscoveryResult ( discoveryResult ) ;
injectServiceDiscoveryResult ( a . getRoster ( ) , presence . getHash ( ) , presence . getVer ( ) , discoveryResult ) ;
} else {
Log . d ( Config . LOGTAG , a . getJid ( ) . asBareJid ( ) + " : mismatch in caps for contact " + jid + " " + presence . getVer ( ) + " vs " + discoveryResult . getVer ( ) ) ;
}
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : unable to fetch caps from " + jid ) ;
}
a . inProgressDiscoFetches . remove ( key ) ;
} ) ;
2019-10-07 07:51:03 +00:00
}
}
2015-01-08 00:23:53 +00:00
2019-10-07 07:51:03 +00:00
private void injectServiceDiscoveryResult ( Roster roster , String hash , String ver , ServiceDiscoveryResult disco ) {
2020-05-30 08:57:22 +00:00
for ( final Contact contact : roster . getContacts ( ) ) {
for ( final Presence presence : contact . getPresences ( ) . getPresences ( ) ) {
2019-10-07 07:51:03 +00:00
if ( hash . equals ( presence . getHash ( ) ) & & ver . equals ( presence . getVer ( ) ) ) {
presence . setServiceDiscoveryResult ( disco ) ;
2018-12-30 15:57:37 +00:00
}
}
2019-10-07 07:51:03 +00:00
}
}
public void fetchMamPreferences ( Account account , final OnMamPreferencesFetched callback ) {
final MessageArchiveService . Version version = MessageArchiveService . Version . get ( account ) ;
IqPacket request = new IqPacket ( IqPacket . TYPE . GET ) ;
request . addChild ( " prefs " , version . namespace ) ;
sendIqPacket ( account , request , ( account1 , packet ) - > {
Element prefs = packet . findChild ( " prefs " , version . namespace ) ;
if ( packet . getType ( ) = = IqPacket . TYPE . RESULT & & prefs ! = null ) {
callback . onPreferencesFetched ( prefs ) ;
} else {
callback . onPreferencesFetchFailed ( ) ;
}
2018-12-30 15:57:37 +00:00
} ) ;
}
2019-10-07 07:51:03 +00:00
public PushManagementService getPushManagementService ( ) {
return mPushManagementService ;
}
2018-06-18 12:15:19 +00:00
2019-10-07 07:51:03 +00:00
public void changeStatus ( Account account , PresenceTemplate template , String signature ) {
if ( ! template . getStatusMessage ( ) . isEmpty ( ) ) {
databaseBackend . insertPresenceTemplate ( template ) ;
2018-11-25 19:58:48 +00:00
}
2019-10-07 07:51:03 +00:00
account . setPgpSignature ( signature ) ;
account . setPresenceStatus ( template . getStatus ( ) ) ;
account . setPresenceStatusMessage ( template . getStatusMessage ( ) ) ;
databaseBackend . updateAccount ( account ) ;
sendPresence ( account ) ;
2018-11-25 19:58:48 +00:00
}
2019-10-07 07:51:03 +00:00
public List < PresenceTemplate > getPresenceTemplates ( Account account ) {
List < PresenceTemplate > templates = databaseBackend . getPresenceTemplates ( ) ;
for ( PresenceTemplate template : account . getSelfContact ( ) . getPresences ( ) . asTemplates ( ) ) {
if ( ! templates . contains ( template ) ) {
templates . add ( 0 , template ) ;
}
}
return templates ;
}
2016-04-11 20:20:32 +00:00
2019-10-07 07:51:03 +00:00
public void saveConversationAsBookmark ( Conversation conversation , String name ) {
final Account account = conversation . getAccount ( ) ;
final Bookmark bookmark = new Bookmark ( account , conversation . getJid ( ) . asBareJid ( ) ) ;
final String nick = conversation . getJid ( ) . getResource ( ) ;
if ( nick ! = null & & ! nick . isEmpty ( ) & & ! nick . equals ( MucOptions . defaultNick ( account ) ) ) {
bookmark . setNick ( nick ) ;
}
if ( ! TextUtils . isEmpty ( name ) ) {
bookmark . setBookmarkName ( name ) ;
}
bookmark . setAutojoin ( getPreferences ( ) . getBoolean ( " autojoin " , getResources ( ) . getBoolean ( R . bool . autojoin ) ) ) ;
createBookmark ( account , bookmark ) ;
bookmark . setConversation ( conversation ) ;
}
2018-11-25 19:58:48 +00:00
2019-10-07 07:51:03 +00:00
public boolean verifyFingerprints ( Contact contact , List < XmppUri . Fingerprint > fingerprints ) {
boolean performedVerification = false ;
final AxolotlService axolotlService = contact . getAccount ( ) . getAxolotlService ( ) ;
for ( XmppUri . Fingerprint fp : fingerprints ) {
if ( fp . type = = XmppUri . FingerprintType . OMEMO ) {
String fingerprint = " 05 " + fp . fingerprint . replaceAll ( " \\ s " , " " ) ;
FingerprintStatus fingerprintStatus = axolotlService . getFingerprintTrust ( fingerprint ) ;
if ( fingerprintStatus ! = null ) {
if ( ! fingerprintStatus . isVerified ( ) ) {
performedVerification = true ;
axolotlService . setFingerprintTrust ( fingerprint , fingerprintStatus . toVerified ( ) ) ;
2018-11-25 19:58:48 +00:00
}
} else {
2019-10-07 07:51:03 +00:00
axolotlService . preVerifyFingerprint ( contact , fingerprint ) ;
2018-11-25 19:58:48 +00:00
}
}
2019-10-07 07:51:03 +00:00
}
return performedVerification ;
2018-11-25 19:58:48 +00:00
}
2019-10-07 07:51:03 +00:00
public boolean verifyFingerprints ( Account account , List < XmppUri . Fingerprint > fingerprints ) {
final AxolotlService axolotlService = account . getAxolotlService ( ) ;
boolean verifiedSomething = false ;
for ( XmppUri . Fingerprint fp : fingerprints ) {
if ( fp . type = = XmppUri . FingerprintType . OMEMO ) {
String fingerprint = " 05 " + fp . fingerprint . replaceAll ( " \\ s " , " " ) ;
Log . d ( Config . LOGTAG , " trying to verify own fp= " + fingerprint ) ;
FingerprintStatus fingerprintStatus = axolotlService . getFingerprintTrust ( fingerprint ) ;
if ( fingerprintStatus ! = null ) {
if ( ! fingerprintStatus . isVerified ( ) ) {
axolotlService . setFingerprintTrust ( fingerprint , fingerprintStatus . toVerified ( ) ) ;
verifiedSomething = true ;
2019-01-17 16:55:47 +00:00
}
2019-10-07 07:51:03 +00:00
} else {
axolotlService . preVerifyFingerprint ( account , fingerprint ) ;
verifiedSomething = true ;
2019-01-17 16:55:47 +00:00
}
}
}
2019-10-07 07:51:03 +00:00
return verifiedSomething ;
2019-01-17 16:55:47 +00:00
}
2019-10-07 07:51:03 +00:00
public boolean blindTrustBeforeVerification ( ) {
return getBooleanPreference ( SettingsActivity . BLIND_TRUST_BEFORE_VERIFICATION , R . bool . btbv ) ;
2018-10-19 21:29:17 +00:00
}
2019-10-07 07:51:03 +00:00
public ShortcutService getShortcutService ( ) {
return mShortcutService ;
}
public void pushMamPreferences ( Account account , Element prefs ) {
IqPacket set = new IqPacket ( IqPacket . TYPE . SET ) ;
set . addChild ( prefs ) ;
sendIqPacket ( account , set , null ) ;
}
2020-01-20 09:54:55 +00:00
public void evictPreview ( String uuid ) {
if ( mBitmapCache . remove ( uuid ) ! = null ) {
2020-03-02 10:11:23 +00:00
Log . d ( Config . LOGTAG , " deleted cached preview " ) ;
2020-01-20 09:54:55 +00:00
}
}
2019-10-07 07:51:03 +00:00
public interface OnMamPreferencesFetched {
void onPreferencesFetched ( Element prefs ) ;
void onPreferencesFetchFailed ( ) ;
}
public interface OnAccountCreated {
void onAccountCreated ( Account account ) ;
void informUser ( int r ) ;
}
public interface OnMoreMessagesLoaded {
void onMoreMessagesLoaded ( int count , Conversation conversation ) ;
void informUser ( int r ) ;
}
public interface OnAccountPasswordChanged {
void onPasswordChangeSucceeded ( ) ;
void onPasswordChangeFailed ( ) ;
}
2015-01-19 10:17:27 +00:00
2018-12-30 15:57:37 +00:00
public interface OnRoomDestroy {
void onRoomDestroySucceeded ( ) ;
void onRoomDestroyFailed ( ) ;
}
2019-10-07 07:51:03 +00:00
public interface OnAffiliationChanged {
void onAffiliationChangedSuccessful ( Jid jid ) ;
2015-01-19 10:17:27 +00:00
2019-10-07 07:51:03 +00:00
void onAffiliationChangeFailed ( Jid jid , int resId ) ;
}
2015-01-19 10:17:27 +00:00
2019-10-07 07:51:03 +00:00
public interface OnConversationUpdate {
void onConversationUpdate ( ) ;
}
2015-01-19 10:17:27 +00:00
2020-04-07 11:15:24 +00:00
public interface OnJingleRtpConnectionUpdate {
2020-04-08 07:42:06 +00:00
void onJingleRtpConnectionUpdate ( final Account account , final Jid with , final String sessionId , final RtpEndUserState state ) ;
2020-04-13 10:02:34 +00:00
void onAudioDeviceChanged ( AppRTCAudioManager . AudioDevice selectedAudioDevice , Set < AppRTCAudioManager . AudioDevice > availableAudioDevices ) ;
2020-04-07 11:15:24 +00:00
}
2019-10-07 07:51:03 +00:00
public interface OnAccountUpdate {
void onAccountUpdate ( ) ;
}
2015-01-19 10:17:27 +00:00
2019-10-07 07:51:03 +00:00
public interface OnCaptchaRequested {
void onCaptchaRequested ( Account account , String id , Data data , Bitmap captcha ) ;
}
2015-10-11 11:11:50 +00:00
2019-10-07 07:51:03 +00:00
public interface OnRosterUpdate {
void onRosterUpdate ( ) ;
}
2015-01-19 10:17:27 +00:00
2019-10-07 07:51:03 +00:00
public interface OnMucRosterUpdate {
void onMucRosterUpdate ( ) ;
}
2015-01-19 10:17:27 +00:00
2019-10-07 07:51:03 +00:00
public interface OnConferenceConfigurationFetched {
void onConferenceConfigurationFetched ( Conversation conversation ) ;
2015-10-22 09:20:36 +00:00
2019-10-07 07:51:03 +00:00
void onFetchFailed ( Conversation conversation , Element error ) ;
}
2015-10-04 22:45:16 +00:00
2019-10-07 07:51:03 +00:00
public interface OnConferenceJoined {
void onConferenceJoined ( Conversation conversation ) ;
}
2015-11-25 19:47:02 +00:00
2019-10-07 07:51:03 +00:00
public interface OnConfigurationPushed {
void onPushSucceeded ( ) ;
2015-01-19 10:17:27 +00:00
2019-10-07 07:51:03 +00:00
void onPushFailed ( ) ;
}
2015-01-19 10:17:27 +00:00
2019-10-07 07:51:03 +00:00
public interface OnShowErrorToast {
void onShowErrorToast ( int resId ) ;
}
2015-07-10 11:28:50 +00:00
2019-10-07 07:51:03 +00:00
public class XmppConnectionBinder extends Binder {
public XmppConnectionService getService ( ) {
return XmppConnectionService . this ;
}
}
2018-09-06 21:17:37 +00:00
2019-10-07 07:51:03 +00:00
private class InternalEventReceiver extends BroadcastReceiver {
2018-09-06 21:17:37 +00:00
@Override
public void onReceive ( Context context , Intent intent ) {
2019-10-07 07:51:03 +00:00
onStartCommand ( intent , 0 , 0 ) ;
2018-09-06 21:17:37 +00:00
}
}
2020-04-15 20:40:37 +00:00
public static class OngoingCall {
private final AbstractJingleConnection . Id id ;
private final Set < Media > media ;
public OngoingCall ( AbstractJingleConnection . Id id , Set < Media > media ) {
this . id = id ;
this . media = media ;
}
@Override
public boolean equals ( Object o ) {
if ( this = = o ) return true ;
if ( o = = null | | getClass ( ) ! = o . getClass ( ) ) return false ;
OngoingCall that = ( OngoingCall ) o ;
return Objects . equal ( id , that . id ) ;
}
@Override
public int hashCode ( ) {
return Objects . hashCode ( id ) ;
}
}
2014-04-07 21:58:59 +00:00
}