diff --git a/.travis.yml b/.travis.yml index 9900afd6f2..438f681437 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,9 +20,6 @@ android: licenses: - '.+' -before_install: -- yes | sdkmanager "platforms;android-27" - install: # Check install section: http://docs.travis-ci.com/user/build-configuration/#install # If you'd like to skip the install stage entirely, set it to true and nothing will be run. diff --git a/build.gradle b/build.gradle index a866675c2f..1d68e19cee 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { maven { url 'https://maven.fabric.io/public' } } dependencies { - classpath 'com.android.tools.build:gradle:3.3.2' + classpath 'com.android.tools.build:gradle:3.4.1' classpath "io.realm:realm-gradle-plugin:3.1.1" classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'com.frogermcs.androiddevmetrics:androiddevmetrics-plugin:0.4' diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000000..6f7b22ee93 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +android.enableJetifier=false +android.useAndroidX=false +android.enableR8=false +android.enableUnitTestBinaryResources=false diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f83d2a335a..0d3fb51ff3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Feb 15 11:01:28 YEKT 2019 +#Thu Apr 18 14:07:31 YEKT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/xabber/build.gradle b/xabber/build.gradle index 55ad17e937..53b172ee52 100644 --- a/xabber/build.gradle +++ b/xabber/build.gradle @@ -10,8 +10,9 @@ android { defaultConfig { minSdkVersion 15 targetSdkVersion 28 - versionCode 602 - versionName '2.6.3(602)' + versionCode 634 + versionName '2.6.4(634)' + manifestPlaceholders = [crashlytics:getLocalProperty("crashlytics.key")] } lintOptions { @@ -95,6 +96,12 @@ android { universalApk true } } + + testOptions { + unitTests { + includeAndroidResources = true + } + } } def build_param = "${build}"; @@ -108,6 +115,17 @@ if (build_param == "open") { } } +def getLocalProperty(String propertyName) { + def propsFile = rootProject.file('local.properties') + if (propsFile.exists()) { + def props = new Properties() + props.load(new FileInputStream(propsFile)) + return props[propertyName] + } else { + return "" + } +} + ext { smackVersion = '4.2.1-SNAPSHOT' supportVersion = '28.0.0' @@ -180,9 +198,14 @@ dependencies { debugImplementation 'com.github.markzhai:blockcanary-android:1.5.0' releaseImplementation 'com.github.markzhai:blockcanary-no-op:1.5.0' + // test + testImplementation 'junit:junit:4.12' + testImplementation "org.robolectric:robolectric:4.0" + testImplementation "org.robolectric:shadows-multidex:4.0.1" } apply plugin: 'com.google.gms.google-services' configurations { all*.exclude group: 'xpp3', module: 'xpp3' + all*.exclude group: 'com.google.guava', module:'guava-jdk5' } diff --git a/xabber/src/dev/res/values/preferences.xml b/xabber/src/dev/res/values/preferences.xml index 224b22e6ad..eb82acccb9 100644 --- a/xabber/src/dev/res/values/preferences.xml +++ b/xabber/src/dev/res/values/preferences.xml @@ -17,5 +17,6 @@ true true + false diff --git a/xabber/src/dev/res/xml/preference_debug.xml b/xabber/src/dev/res/xml/preference_debug.xml index 983205d8b4..db03248080 100644 --- a/xabber/src/dev/res/xml/preference_debug.xml +++ b/xabber/src/dev/res/xml/preference_debug.xml @@ -53,4 +53,18 @@ android:title="@string/debug_fetch_crowdfunding_feed_title"> + + + + + + \ No newline at end of file diff --git a/xabber/src/main/AndroidManifest.xml b/xabber/src/main/AndroidManifest.xml index 68d6000156..999b60e3d7 100644 --- a/xabber/src/main/AndroidManifest.xml +++ b/xabber/src/main/AndroidManifest.xml @@ -49,6 +49,12 @@ android:label="@string/application_title_full" android:theme="@style/Theme" tools:replace="label, icon, allowBackup"> + + + + + + + \ No newline at end of file diff --git a/xabber/src/main/java/com/xabber/android/data/ActivityManager.java b/xabber/src/main/java/com/xabber/android/data/ActivityManager.java index 9c1947ed60..42ba4f7c89 100644 --- a/xabber/src/main/java/com/xabber/android/data/ActivityManager.java +++ b/xabber/src/main/java/com/xabber/android/data/ActivityManager.java @@ -23,7 +23,11 @@ import com.xabber.android.R; import com.xabber.android.data.account.AccountManager; import com.xabber.android.data.connection.CertificateManager; +import com.xabber.android.data.extension.avatar.AvatarManager; import com.xabber.android.data.log.LogManager; +import com.xabber.android.data.push.SyncManager; +import com.xabber.android.data.roster.RosterManager; +import com.xabber.android.service.XabberService; import com.xabber.android.ui.activity.AboutActivity; import com.xabber.android.ui.activity.ContactListActivity; import com.xabber.android.ui.activity.LoadActivity; @@ -42,6 +46,7 @@ public class ActivityManager implements OnUnloadListener { private static final String EXTRA_TASK_INDEX = "com.xabber.android.data.ActivityManager.EXTRA_TASK_INDEX"; + private static final long START_SERVICE_DELAY = 1000; private static final boolean LOG = true; private static ActivityManager instance; @@ -201,11 +206,30 @@ public void onResume(final Activity activity) { if (LOG) { LogManager.i(activity, "onResume"); } - if (!application.isInitialized() && !(activity instanceof LoadActivity)) { + if((!application.isInitialized() || SyncManager.getInstance().isSyncMode()) + && !Application.getInstance().isClosing()) { + if (LOG) { LogManager.i(this, "Wait for loading"); } - activity.startActivity(LoadActivity.createIntent(activity)); + AccountManager.getInstance().onPreInitialize(); + RosterManager.getInstance().onPreInitialize(); + Application.getInstance().runInBackground(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(START_SERVICE_DELAY); + } catch (InterruptedException e) { + e.printStackTrace(); + } + Application.getInstance().runOnUiThread(new Runnable() { + @Override + public void run() { + activity.startService(XabberService.createIntent(activity)); + } + }); + } + }); } if (onErrorListener != null) { application.removeUIListener(OnErrorListener.class, onErrorListener); @@ -221,6 +245,7 @@ public void onError(final int resourceId) { CertificateManager.getInstance().registerActivity(activity); AccountManager.getInstance().stopGracePeriod(); + SyncManager.getInstance().onActivityResume(); } /** diff --git a/xabber/src/main/java/com/xabber/android/data/Application.java b/xabber/src/main/java/com/xabber/android/data/Application.java index 6cd0bf4f21..cf991341c2 100644 --- a/xabber/src/main/java/com/xabber/android/data/Application.java +++ b/xabber/src/main/java/com/xabber/android/data/Application.java @@ -44,7 +44,8 @@ import com.xabber.android.data.extension.chat_markers.ChatMarkerManager; import com.xabber.android.data.extension.cs.ChatStateManager; import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager; -import com.xabber.android.data.extension.mam.MamManager; +import com.xabber.android.data.extension.iqlast.LastActivityInteractor; +import com.xabber.android.data.extension.mam.NextMamManager; import com.xabber.android.data.extension.muc.MUCManager; import com.xabber.android.data.extension.otr.OTRManager; import com.xabber.android.data.extension.ssn.SSNManager; @@ -56,8 +57,11 @@ import com.xabber.android.data.message.ReceiptManager; import com.xabber.android.data.message.chat.ChatManager; import com.xabber.android.data.message.phrase.PhraseManager; +import com.xabber.android.data.notification.DelayedNotificationActionManager; import com.xabber.android.data.notification.NotificationManager; import com.xabber.android.data.notification.custom_notification.CustomNotifyPrefsManager; +import com.xabber.android.data.push.PushManager; +import com.xabber.android.data.push.SyncManager; import com.xabber.android.data.roster.GroupManager; import com.xabber.android.data.roster.PresenceManager; import com.xabber.android.data.roster.RosterManager; @@ -349,6 +353,7 @@ public void onCreate() { } private void addManagers() { + addManager(SyncManager.getInstance()); addManager(SettingsManager.getInstance()); addManager(LogManager.getInstance()); addManager(DatabaseManager.getInstance()); @@ -383,9 +388,12 @@ private void addManagers() { addManager(CarbonManager.getInstance()); addManager(HttpFileUploadManager.getInstance()); addManager(BlockingManager.getInstance()); - addManager(MamManager.getInstance()); + addManager(NextMamManager.getInstance()); addManager(CertificateManager.getInstance()); addManager(XMPPAuthManager.getInstance()); + addManager(PushManager.getInstance()); + addManager(DelayedNotificationActionManager.getInstance()); + addManager(LastActivityInteractor.getInstance()); } /** @@ -603,4 +611,7 @@ public void runOnUiThreadDelay(final Runnable runnable, long delayMillis) { handler.postDelayed(runnable, delayMillis); } + public boolean isServiceStarted() { + return serviceStarted; + } } diff --git a/xabber/src/main/java/com/xabber/android/data/SettingsManager.java b/xabber/src/main/java/com/xabber/android/data/SettingsManager.java index 59e0ef1a60..68895142be 100644 --- a/xabber/src/main/java/com/xabber/android/data/SettingsManager.java +++ b/xabber/src/main/java/com/xabber/android/data/SettingsManager.java @@ -327,10 +327,10 @@ public static boolean eventsLightningForMuc() { R.bool.events_lightning_default); } - public static boolean eventsPersistent() { - return getBoolean(R.string.events_persistent_key, - R.bool.events_persistent_default); - } +// public static boolean eventsPersistent() { +// return getBoolean(R.string.events_persistent_key, +// R.bool.events_persistent_default); +// } public static boolean eventsShowText() { return getNotifBoolean(R.string.events_show_text_key, @@ -367,9 +367,8 @@ public static boolean eventsOnMuc() { // R.bool.events_in_app_preview_default); // } - @Deprecated public static boolean eventsInChatSounds() { - return getBoolean(R.string.events_in_chat_sounds_key, + return getNotifBoolean(R.string.events_in_chat_sounds_key, R.bool.events_in_chat_sounds_default); } @@ -528,6 +527,11 @@ public static boolean connectionUseCarbons() { R.bool.connection_use_carbons_default); } + public static boolean connectionCompressImage() { + return getBoolean(R.string.connection_compress_image_on_upload_key, + R.bool.connection_compress_image_on_upload_default); + } + public static DnsResolverType connectionDnsResolver() { String value = getString(R.string.connection_dns_resolver_type_key, R.string.connection_dns_resolver_type_default); @@ -600,6 +604,10 @@ public static boolean useDevelopAPI() { return getBoolean(R.string.debug_use_develop_api_key, R.bool.debug_use_develop_api_default); } + public static boolean syncBookmarksOnStart() { + return getBoolean(R.string.debug_sync_bookmarks_on_start_key, R.bool.debug_sync_bookmarks_on_start_default); + } + public static boolean isCrashReportsSupported() { return BuildConfig.FLAVOR.equals("beta") || BuildConfig.FLAVOR.equals("vip") @@ -854,6 +862,14 @@ public static int getLastCrowdfundingPosition() { return getInteger(R.string.crowdfunding_last_position_key, 0); } + public static void setEnabledPushNodes(String enabledPushNodes) { + setString(R.string.enabled_push_nodes, enabledPushNodes); + } + + public static String getEnabledPushNodes() { + return getString(R.string.enabled_push_nodes, ""); + } + public static void resetPreferences(Context context, String preferencesName) { context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE).edit().clear().apply(); } diff --git a/xabber/src/main/java/com/xabber/android/data/TestApplication.java b/xabber/src/main/java/com/xabber/android/data/TestApplication.java new file mode 100644 index 0000000000..04e1f4ac30 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/TestApplication.java @@ -0,0 +1,596 @@ +/** + * Copyright (c) 2013, Redsolution LTD. All rights reserved. + * + * This file is part of Xabber project; you can redistribute it and/or + * modify it under the terms of the GNU General Public License, Version 3. + * + * Xabber is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License, + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.xabber.android.data; + +import android.app.Activity; +import android.content.Context; +import android.os.Handler; +import android.os.StrictMode; +import android.support.annotation.NonNull; +import android.support.multidex.MultiDex; + +import com.crashlytics.android.Crashlytics; +import com.crashlytics.android.core.CrashlyticsCore; +import com.frogermcs.androiddevmetrics.AndroidDevMetrics; +import com.github.moduth.blockcanary.BlockCanary; +import com.squareup.leakcanary.LeakCanary; +import com.xabber.android.BuildConfig; +import com.xabber.android.R; +import com.xabber.android.data.account.AccountManager; +import com.xabber.android.data.account.ScreenManager; +import com.xabber.android.data.connection.CertificateManager; +import com.xabber.android.data.connection.ConnectionManager; +import com.xabber.android.data.connection.NetworkManager; +import com.xabber.android.data.connection.ReconnectionManager; +import com.xabber.android.data.database.DatabaseManager; +import com.xabber.android.data.extension.attention.AttentionManager; +import com.xabber.android.data.extension.avatar.AvatarManager; +import com.xabber.android.data.extension.avatar.AvatarStorage; +import com.xabber.android.data.extension.blocking.BlockingManager; +import com.xabber.android.data.extension.capability.CapabilitiesManager; +import com.xabber.android.data.extension.carbons.CarbonManager; +import com.xabber.android.data.extension.chat_markers.ChatMarkerManager; +import com.xabber.android.data.extension.cs.ChatStateManager; +import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager; +import com.xabber.android.data.extension.iqlast.LastActivityInteractor; +import com.xabber.android.data.extension.mam.NextMamManager; +import com.xabber.android.data.extension.muc.MUCManager; +import com.xabber.android.data.extension.otr.OTRManager; +import com.xabber.android.data.extension.ssn.SSNManager; +import com.xabber.android.data.extension.vcard.VCardManager; +import com.xabber.android.data.http.CrowdfundingManager; +import com.xabber.android.data.http.PatreonManager; +import com.xabber.android.data.log.LogManager; +import com.xabber.android.data.message.MessageManager; +import com.xabber.android.data.message.ReceiptManager; +import com.xabber.android.data.message.chat.ChatManager; +import com.xabber.android.data.message.phrase.PhraseManager; +import com.xabber.android.data.notification.DelayedNotificationActionManager; +import com.xabber.android.data.notification.NotificationManager; +import com.xabber.android.data.notification.custom_notification.CustomNotifyPrefsManager; +import com.xabber.android.data.push.PushManager; +import com.xabber.android.data.push.SyncManager; +import com.xabber.android.data.roster.GroupManager; +import com.xabber.android.data.roster.PresenceManager; +import com.xabber.android.data.roster.RosterManager; +import com.xabber.android.data.xaccount.XMPPAuthManager; +import com.xabber.android.data.xaccount.XabberAccountManager; +import com.xabber.android.service.XabberService; +import com.xabber.android.utils.AppBlockCanaryContext; + +import org.jivesoftware.smack.provider.ProviderFileLoader; +import org.jivesoftware.smack.provider.ProviderManager; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; + +import io.fabric.sdk.android.Fabric; + +/** + * Base entry point. + * + * @author alexander.ivanov + */ +public class TestApplication extends android.app.Application { + + private static final String LOG_TAG = Application.class.getSimpleName(); + private static TestApplication instance; + private final ArrayList registeredManagers; + /** + * Thread to execute tasks in background.. + */ + private final ExecutorService backgroundExecutor; + private final ExecutorService backgroundExecutorForUserActions; + /** + * Handler to execute runnable in UI thread. + */ + private final Handler handler; + /** + * Unmodifiable collections of managers that implement some common + * interface. + */ + private Map, Collection> managerInterfaces; + private Map, Collection> uiListeners; + /** + * Where data load was requested. + */ + private boolean serviceStarted; + /** + * Whether application was initialized. + */ + private boolean initialized; + /** + * Whether user was notified about some action in contact list activity + * after application initialization. + */ + private boolean notified; + /** + * Whether application is to be closed. + */ + private boolean closing; + /** + * Whether {@link #onServiceDestroy()} has been called. + */ + private boolean closed; + + private final Runnable timerRunnable = new Runnable() { + + @Override + public void run() { + for (OnTimerListener listener : getManagers(OnTimerListener.class)) { + listener.onTimer(); + } + if (!closing) { + startTimer(); + } + } + + }; + /** + * Future for loading process. + */ + private Future loadFuture; + + public TestApplication() { + instance = this; + serviceStarted = false; + initialized = false; + notified = false; + closing = false; + closed = false; + uiListeners = new HashMap<>(); + managerInterfaces = new HashMap<>(); + registeredManagers = new ArrayList<>(); + + handler = new Handler(); + backgroundExecutor = createSingleThreadExecutor("Background executor service"); + backgroundExecutorForUserActions = Executors.newFixedThreadPool( + Runtime.getRuntime().availableProcessors(), + new ThreadFactory() { + @Override + public Thread newThread(@NonNull Runnable runnable) { + Thread thread = new Thread(runnable); + thread.setPriority(Thread.MIN_PRIORITY); + thread.setDaemon(true); + return thread; + } + }); + } + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + MultiDex.install(this); + } + + @NonNull + private ExecutorService createSingleThreadExecutor(final String threadName) { + return Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(@NonNull Runnable runnable) { + Thread thread = new Thread(runnable, threadName); + thread.setPriority(Thread.MIN_PRIORITY); + thread.setDaemon(true); + return thread; + } + }); + } + + public static TestApplication getInstance() { + if (instance == null) { + throw new IllegalStateException(); + } + return instance; + } + + /** + * Whether application is initialized. + */ + public boolean isInitialized() { + return initialized; + } + + private void onLoad() { + ProviderManager.addLoader(new ProviderFileLoader(getResources().openRawResource(R.raw.smack))); + + for (OnLoadListener listener : getManagers(OnLoadListener.class)) { + LogManager.i(listener, "onLoad"); + listener.onLoad(); + } + } + + private void onInitialized() { + for (OnInitializedListener listener : getManagers(OnInitializedListener.class)) { + LogManager.i(listener, "onInitialized"); + listener.onInitialized(); + } + initialized = true; + XabberService.getInstance().changeForeground(); + startTimer(); + } + + private void onClose() { + LogManager.i(LOG_TAG, "onClose1"); + for (Object manager : registeredManagers) { + if (manager instanceof OnCloseListener) { + ((OnCloseListener) manager).onClose(); + } + } + closed = true; + LogManager.i(LOG_TAG, "onClose2"); + } + + void onUnload() { + LogManager.i(LOG_TAG, "onUnload1"); + for (Object manager : registeredManagers) { + if (manager instanceof OnUnloadListener) { + ((OnUnloadListener) manager).onUnload(); + } + } + LogManager.i(LOG_TAG, "onUnload2"); + android.os.Process.killProcess(android.os.Process.myPid()); + } + + /** + * @return true only once per application life. Subsequent + * calls will always returns false. + */ + public boolean doNotify() { + if (notified) { + return false; + } + notified = true; + return true; + } + + /** + * Starts data loading in background if not started yet. + */ + public void onServiceStarted() { + if (serviceStarted) { + return; + } + serviceStarted = true; + LogManager.i(this, "onStart"); + loadFuture = backgroundExecutor.submit(new Callable() { + @Override + public Void call() throws Exception { + try { + onLoad(); + } finally { + runOnUiThread(new Runnable() { + @Override + public void run() { + // Throw exceptions in UI thread if any. + try { + loadFuture.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + onInitialized(); + } + }); + } + return null; + } + }); + } + + /** + * Requests to close application in some time in future. + */ + public void requestToClose() { + LogManager.i(LOG_TAG, "requestToClose1"); + closing = true; + stopService(XabberService.createIntent(this)); + LogManager.i(LOG_TAG, "requestToClose2"); + } + + /** + * @return Whether application is to be closed. + */ + public boolean isClosing() { + return closing; + } + + @Override + public void onCreate() { + super.onCreate(); + + /** Crashlytics */ + CrashlyticsCore crashlyticsCore = new CrashlyticsCore.Builder() + .disabled(BuildConfig.DEBUG || BuildConfig.FLAVOR == "open") + .build(); + Fabric.with(this, new Crashlytics.Builder().core(crashlyticsCore).build()); + + Thread.currentThread().setPriority(Thread.MAX_PRIORITY); + //addManagers(); + //DatabaseManager.getInstance().addTables(); + LogManager.i(this, "onCreate finished..."); + } + + private void addManagers() { + addManager(SyncManager.getInstance()); + addManager(SettingsManager.getInstance()); + addManager(LogManager.getInstance()); + addManager(DatabaseManager.getInstance()); + addManager(AvatarStorage.getInstance()); + addManager(OTRManager.getInstance()); + addManager(ConnectionManager.getInstance()); + addManager(ScreenManager.getInstance()); + addManager(AccountManager.getInstance()); + addManager(XabberAccountManager.getInstance()); + addManager(PatreonManager.getInstance()); + addManager(CrowdfundingManager.getInstance()); + addManager(MUCManager.getInstance()); + addManager(MessageManager.getInstance()); + addManager(ChatManager.getInstance()); + addManager(VCardManager.getInstance()); + addManager(AvatarManager.getInstance()); + addManager(PresenceManager.getInstance()); + addManager(RosterManager.getInstance()); + addManager(GroupManager.getInstance()); + addManager(PhraseManager.getInstance()); + addManager(NotificationManager.getInstance()); + addManager(CustomNotifyPrefsManager.getInstance()); + addManager(ActivityManager.getInstance()); + addManager(CapabilitiesManager.getInstance()); + addManager(ChatStateManager.getInstance()); + addManager(NetworkManager.getInstance()); + addManager(ReconnectionManager.getInstance()); + addManager(ReceiptManager.getInstance()); + addManager(ChatMarkerManager.getInstance()); + addManager(SSNManager.getInstance()); + addManager(AttentionManager.getInstance()); + addManager(CarbonManager.getInstance()); + addManager(HttpFileUploadManager.getInstance()); + addManager(BlockingManager.getInstance()); + addManager(NextMamManager.getInstance()); + addManager(CertificateManager.getInstance()); + addManager(XMPPAuthManager.getInstance()); + addManager(PushManager.getInstance()); + addManager(DelayedNotificationActionManager.getInstance()); + addManager(LastActivityInteractor.getInstance()); + } + + /** + * Register new manager. + */ + private void addManager(Object manager) { + registeredManagers.add(manager); + } + + @Override + public void onLowMemory() { + for (OnLowMemoryListener listener : getManagers(OnLowMemoryListener.class)) { + listener.onLowMemory(); + } + super.onLowMemory(); + } + + /** + * Service have been destroyed. + */ + public void onServiceDestroy() { + LogManager.i(LOG_TAG, "onServiceDestroy"); + + if (closed) { + LogManager.i(LOG_TAG, "onServiceDestroy closed"); + return; + } + onClose(); + + // use new thread instead of run in background to exit immediately + // without waiting for possible other threads in executor + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + onUnload(); + } + }); + thread.setPriority(Thread.MIN_PRIORITY); + thread.setDaemon(true); + thread.start(); + } + + @Override + public void onTerminate() { + requestToClose(); + super.onTerminate(); + } + + /** + * Start periodically callbacks. + */ + private void startTimer() { + runOnUiThreadDelay(timerRunnable, OnTimerListener.DELAY); + } + + /** + * @param cls Requested class of managers. + * @return List of registered manager. + */ + @SuppressWarnings("unchecked") + public Collection getManagers(Class cls) { + if (closed) { + return Collections.emptyList(); + } + Collection collection = (Collection) managerInterfaces.get(cls); + if (collection == null) { + collection = new ArrayList<>(); + for (Object manager : registeredManagers) { + if (cls.isInstance(manager)) { + collection.add((T) manager); + } + } + collection = Collections.unmodifiableCollection(collection); + managerInterfaces.put(cls, collection); + } + return collection; + } + + /** + * Request to clear application data. + */ + public void requestToClear() { + runInBackground(new Runnable() { + @Override + public void run() { + clear(); + } + }); + } + + private void clear() { + for (Object manager : registeredManagers) { + if (manager instanceof OnClearListener) { + ((OnClearListener) manager).onClear(); + } + } + } + + /** + * Request to wipe all sensitive application data. + */ + public void requestToWipe() { + runInBackground(new Runnable() { + @Override + public void run() { + clear(); + for (Object manager : registeredManagers) + if (manager instanceof OnWipeListener) + ((OnWipeListener) manager).onWipe(); + } + }); + } + + @SuppressWarnings("unchecked") + private Collection getOrCreateUIListeners(Class cls) { + Collection collection = (Collection) uiListeners.get(cls); + if (collection == null) { + collection = new ArrayList(); + uiListeners.put(cls, collection); + } + return collection; + } + + /** + * @param cls Requested class of listeners. + * @return List of registered UI listeners. + */ + public Collection getUIListeners(Class cls) { + if (closed) { + return Collections.emptyList(); + } + return Collections.unmodifiableCollection(getOrCreateUIListeners(cls)); + } + + /** + * Register new listener. + *

+ * Should be called from {@link Activity#onResume()}. + */ + public void addUIListener(Class cls, T listener) { + getOrCreateUIListeners(cls).add(listener); + } + + /** + * Unregister listener. + *

+ * Should be called from {@link Activity#onPause()}. + */ + public void removeUIListener(Class cls, T listener) { + getOrCreateUIListeners(cls).remove(listener); + } + + /** + * Notify about error. + */ + public void onError(final int resourceId) { + runOnUiThread(new Runnable() { + @Override + public void run() { + for (OnErrorListener onErrorListener : getUIListeners(OnErrorListener.class)) { + onErrorListener.onError(resourceId); + } + } + }); + } + + /** + * Notify about error. + */ + public void onError(NetworkException networkException) { + LogManager.exception(this, networkException); + onError(networkException.getResourceId()); + } + + /** + * Submits request to be executed in background. + */ + public void runInBackground(final Runnable runnable) { + backgroundExecutor.submit(new Runnable() { + @Override + public void run() { + try { + runnable.run(); + } catch (Exception e) { + LogManager.exception(runnable, e); + } + } + }); + } + + public void runInBackgroundUserRequest(final Runnable runnable) { + backgroundExecutorForUserActions.submit(new Runnable() { + @Override + public void run() { + try { + runnable.run(); + } catch (Exception e) { + LogManager.exception(runnable, e); + } + } + }); + } + + /** + * Submits request to be executed in UI thread. + */ + public void runOnUiThread(final Runnable runnable) { + handler.post(runnable); + } + + /** + * Submits request to be executed in UI thread. + */ + public void runOnUiThreadDelay(final Runnable runnable, long delayMillis) { + handler.postDelayed(runnable, delayMillis); + } + + public boolean isServiceStarted() { + return serviceStarted; + } +} + diff --git a/xabber/src/main/java/com/xabber/android/data/account/AccountItem.java b/xabber/src/main/java/com/xabber/android/data/account/AccountItem.java index ee932d25b3..d8aebcb905 100644 --- a/xabber/src/main/java/com/xabber/android/data/account/AccountItem.java +++ b/xabber/src/main/java/com/xabber/android/data/account/AccountItem.java @@ -116,6 +116,17 @@ public class AccountItem extends ConnectionItem implements Comparable System.currentTimeMillis(); } + + public String getPushNode() { + return pushNode; + } + + public void setPushNode(String pushNode) { + this.pushNode = pushNode; + } + + public String getPushServiceJid() { + return pushServiceJid; + } + + public void setPushServiceJid(String pushServiceJid) { + this.pushServiceJid = pushServiceJid; + } + + public boolean isPushWasEnabled() { + return pushWasEnabled; + } + + public void setPushWasEnabled(boolean pushWasEnabled) { + boolean changed = false; + if (this.pushWasEnabled != pushWasEnabled) changed = true; + this.pushWasEnabled = pushWasEnabled; + if (changed) AccountManager.getInstance().onAccountChanged(getAccount()); + } + + public void setPushEnabled(boolean enabled) { + this.pushEnabled = enabled; + } + + public boolean isPushEnabled() { + return pushEnabled; + } + + public long getStartHistoryTimestamp() { + return startHistoryTimestamp; + } + + public void setStartHistoryTimestamp(long startHistoryTimestamp) { + this.startHistoryTimestamp = startHistoryTimestamp; + } } diff --git a/xabber/src/main/java/com/xabber/android/data/account/AccountManager.java b/xabber/src/main/java/com/xabber/android/data/account/AccountManager.java index 9f3e559ea0..30ea450311 100644 --- a/xabber/src/main/java/com/xabber/android/data/account/AccountManager.java +++ b/xabber/src/main/java/com/xabber/android/data/account/AccountManager.java @@ -46,12 +46,14 @@ import com.xabber.android.data.database.sqlite.StatusTable; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.extension.mam.LoadHistorySettings; -import com.xabber.android.data.extension.mam.MamManager; +import com.xabber.android.data.extension.mam.NextMamManager; import com.xabber.android.data.extension.vcard.VCardManager; import com.xabber.android.data.log.LogManager; import com.xabber.android.data.notification.BaseAccountNotificationProvider; import com.xabber.android.data.notification.NotificationManager; +import com.xabber.android.data.push.PushManager; import com.xabber.android.data.roster.PresenceManager; +import com.xabber.android.data.roster.RosterCacheManager; import com.xabber.android.data.roster.RosterManager; import com.xabber.android.data.xaccount.XabberAccountManager; @@ -106,6 +108,7 @@ public class AccountManager implements OnLoadListener, OnUnloadListener, OnWipeL * List of accounts. */ private final Map accountItems; + private final List cachedEnabledAccounts; private final BaseAccountNotificationProvider accountErrorProvider; private final Application application; @@ -130,6 +133,7 @@ private AccountManager() { this.application = Application.getInstance(); accountItems = new HashMap<>(); savedStatuses = new ArrayList<>(); + cachedEnabledAccounts = new ArrayList<>(); accountErrorProvider = new BaseAccountNotificationProvider<>(R.drawable.ic_stat_error); colors = application.getResources().getIntArray(R.array.account_color_names).length; @@ -138,6 +142,44 @@ private AccountManager() { xa = false; } + public void onPreInitialize() { + Realm realm = RealmManager.getInstance().getRealmUiThread(); + RealmResults accountRealms = realm.where(AccountRealm.class).findAll(); + + for (AccountRealm accountRealm : accountRealms) { + DomainBareJid serverName = null; + try { + serverName = JidCreate.domainBareFrom(accountRealm.getServerName()); + } catch (XmppStringprepException e) { + LogManager.exception(this, e); + } + + Localpart userName = null; + try { + userName = Localpart.from(accountRealm.getUserName()); + } catch (XmppStringprepException e) { + LogManager.exception(this, e); + } + + Resourcepart resource = null; + try { + resource = Resourcepart.from(accountRealm.getResource()); + } catch (XmppStringprepException e) { + LogManager.exception(this, e); + } + + if (serverName == null || userName == null || resource == null) { + LogManager.e(LOG_TAG, "could not create account. username " + userName + + ", server name " + serverName + + ", resource " + resource); + continue; + } + + if (accountRealm.isEnabled()) + cachedEnabledAccounts.add(AccountJid.from(userName, serverName, resource)); + } + } + @Override public void onLoad() { final Collection savedStatuses = loadSavedStatuses(); @@ -228,6 +270,10 @@ public void onLoad() { accountItem.setLoadHistorySettings(accountRealm.getLoadHistorySettings()); } accountItem.setSuccessfulConnectionHappened(accountRealm.isSuccessfulConnectionHappened()); + accountItem.setPushNode(accountRealm.getPushNode()); + accountItem.setPushServiceJid(accountRealm.getPushServiceJid()); + accountItem.setPushEnabled(accountRealm.isPushEnabled()); + accountItem.setPushWasEnabled(accountRealm.isPushWasEnabled()); accountItems.add(accountItem); @@ -377,6 +423,10 @@ public AccountJid addAccount(String user, String password, String token, boolean throw new NetworkException(R.string.EMPTY_USER_NAME); } + if (user.contains(" ")) { + throw new NetworkException(R.string.INCORRECT_USER_NAME); + } + DomainBareJid serverName; try { serverName = JidCreate.domainBareFrom(user); @@ -459,6 +509,13 @@ private void removeAccountWithoutCallback(final AccountJid account) { return; } + // disable push + PushManager.getInstance().disablePushNotification(getAccount(account), false); + + // remove contacts and account from cache + RosterCacheManager.removeContacts(account); + cachedEnabledAccounts.remove(account); + boolean wasEnabled = accountItem.isEnabled(); accountItem.setEnabled(false); accountItem.disconnect(); @@ -682,39 +739,59 @@ public void setEnabled(AccountJid account, boolean enabled) { return; } + // disable push + if (!enabled) PushManager.getInstance().disablePushNotification(getAccount(account), false); + + // remove from cached if disabled + if (!enabled) cachedEnabledAccounts.remove(account); + accountItem.setEnabled(enabled); requestToWriteAccount(accountItem); + PushManager.getInstance().updateEnabledPushNodes(); } /** * @return List of enabled accounts. */ public Collection getEnabledAccounts() { + Map accountsCopy = new HashMap<>(accountItems); List enabledAccounts = new ArrayList<>(); - for (AccountItem accountItem : accountItems.values()) { + for (AccountItem accountItem : accountsCopy.values()) { if (accountItem.isEnabled()) { AccountJid accountJid = accountItem.getAccount(); accountJid.setOrder(accountItem.getOrder()); enabledAccounts.add(accountJid); } } - return Collections.unmodifiableCollection(enabledAccounts); } + public Collection getCachedEnabledAccounts() { + List copyCachedEnabledAccounts = new ArrayList<>(cachedEnabledAccounts); + return Collections.unmodifiableCollection(copyCachedEnabledAccounts); + } + public boolean hasAccounts() { return !accountItems.isEmpty(); } + public boolean checkAccounts() { + Realm realm = RealmManager.getInstance().getRealmUiThread(); + RealmResults accountRealms = realm.where(AccountRealm.class).findAll(); + return !accountRealms.isEmpty(); + } + /** * @return List of all accounts including disabled. */ public Collection getAllAccounts() { - return Collections.unmodifiableCollection(accountItems.keySet()); + Map accountsCopy = new HashMap<>(accountItems); + return Collections.unmodifiableCollection(accountsCopy.keySet()); } public Collection getAllAccountItems() { - return Collections.unmodifiableCollection(accountItems.values()); + Map accountsCopy = new HashMap<>(accountItems); + return Collections.unmodifiableCollection(accountsCopy.values()); } public CommonState getCommonState() { @@ -965,7 +1042,7 @@ public void setMamDefaultBehaviour(AccountJid accountJid, MamPrefsIQ.DefaultBeha if (!accountItem.getMamDefaultBehaviour().equals(mamDefaultBehavior)) { accountItem.setMamDefaultBehaviour(mamDefaultBehavior); requestToWriteAccount(accountItem); - MamManager.getInstance().requestUpdatePreferences(accountJid); + NextMamManager.getInstance().onRequestUpdatePreferences(accountJid); } } @@ -1208,4 +1285,29 @@ public void stopGracePeriod() { } } + public void setPushNode(AccountItem account, String pushNode, String pushServiceJid) { + account.setPushNode(pushNode); + account.setPushServiceJid(pushServiceJid); + requestToWriteAccount(account); + } + + public void setPushEnabled(final AccountItem accountItem, final boolean enabled) { + accountItem.setPushEnabled(enabled); + requestToWriteAccount(accountItem); + Application.getInstance().runInBackground(new Runnable() { + @Override + public void run() { + if (enabled) PushManager.getInstance().enablePushNotificationsIfNeed(accountItem); + else PushManager.getInstance().disablePushNotification(accountItem, true); + } + }); + PushManager.getInstance().updateEnabledPushNodes(); + } + + public void setPushWasEnabled(AccountItem accountItem, boolean enabled) { + accountItem.setPushWasEnabled(enabled); + requestToWriteAccount(accountItem); + PushManager.getInstance().updateEnabledPushNodes(); + } + } diff --git a/xabber/src/main/java/com/xabber/android/data/account/ScreenManager.java b/xabber/src/main/java/com/xabber/android/data/account/ScreenManager.java index 378000c390..6058115c2c 100644 --- a/xabber/src/main/java/com/xabber/android/data/account/ScreenManager.java +++ b/xabber/src/main/java/com/xabber/android/data/account/ScreenManager.java @@ -102,14 +102,18 @@ public void onScreen(Intent intent) { if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { LogManager.i(LOG_TAG, "onScreen ACTION_SCREEN_ON isOptimizingBattery: " + BatteryHelper.isOptimizingBattery()); -// ConnectionManager.getInstance().updateConnections(false); alarmManager.cancel(goAwayPendingIntent); alarmManager.cancel(goXaPendingIntent); - AccountManager.getInstance().wakeUp(); - AccountManager.getInstance().stopGracePeriod(); - // notify server(s) that client is now active - ClientStateManager.setActive(); + AccountManager.getInstance().stopGracePeriod(); + Application.getInstance().runInBackgroundUserRequest(new Runnable() { + @Override + public void run() { + // notify server(s) that client is now active + AccountManager.getInstance().wakeUp(); + ClientStateManager.setActive(); + } + }); } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { LogManager.i(LOG_TAG, "onScreen ACTION_SCREEN_OFF isOptimizingBattery: " + BatteryHelper.isOptimizingBattery()); @@ -120,8 +124,13 @@ public void onScreen(Intent intent) { alarmManager.set(AlarmManager.RTC_WAKEUP, getTime(goXa), goXaPendingIntent); - // notify server(s) that client is now inactive - ClientStateManager.setInactive(); + Application.getInstance().runInBackgroundUserRequest(new Runnable() { + @Override + public void run() { + // notify server(s) that client is now inactive + ClientStateManager.setInactive(); + } + }); } } diff --git a/xabber/src/main/java/com/xabber/android/data/connection/ConnectionListener.java b/xabber/src/main/java/com/xabber/android/data/connection/ConnectionListener.java index d7da287fbf..1704cd236f 100644 --- a/xabber/src/main/java/com/xabber/android/data/connection/ConnectionListener.java +++ b/xabber/src/main/java/com/xabber/android/data/connection/ConnectionListener.java @@ -8,14 +8,12 @@ import com.xabber.android.data.extension.bookmarks.BookmarksManager; import com.xabber.android.data.extension.carbons.CarbonManager; import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager; -import com.xabber.android.data.extension.mam.MamManager; import com.xabber.android.data.log.LogManager; import com.xabber.android.data.message.MessageManager; import com.xabber.android.data.roster.PresenceManager; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; -import org.jivesoftware.smack.packet.StreamError; import org.jivesoftware.smack.sasl.SASLErrorException; class ConnectionListener implements org.jivesoftware.smack.ConnectionListener { @@ -63,7 +61,6 @@ public void authenticated(XMPPConnection connection, final boolean resumed) { // just to see the order of call CarbonManager.getInstance().onAuthorized(connectionItem); - MamManager.getInstance().onAuthorized(connectionItem); BlockingManager.getInstance().onAuthorized(connectionItem); HttpFileUploadManager.getInstance().onAuthorized(connectionItem); diff --git a/xabber/src/main/java/com/xabber/android/data/connection/ConnectionThread.java b/xabber/src/main/java/com/xabber/android/data/connection/ConnectionThread.java index f6cb85568c..afe89e6605 100644 --- a/xabber/src/main/java/com/xabber/android/data/connection/ConnectionThread.java +++ b/xabber/src/main/java/com/xabber/android/data/connection/ConnectionThread.java @@ -22,6 +22,8 @@ import com.xabber.android.data.extension.forward.ForwardComment; import com.xabber.android.data.extension.forward.ForwardCommentProvider; import com.xabber.android.data.extension.httpfileupload.CustomDataProvider; +import com.xabber.android.data.extension.references.ReferenceElement; +import com.xabber.android.data.extension.references.ReferencesProvider; import com.xabber.android.data.log.AndroidLoggingHandler; import com.xabber.android.data.log.LogManager; import com.xabber.android.data.xaccount.HttpConfirmIq; @@ -126,6 +128,15 @@ void connectAndLogin() { LogManager.i(this, "Use DNS Java resolver"); ExtDNSJavaResolver.setup(); + ProviderManager.addExtensionProvider(DataForm.ELEMENT, + DataForm.NAMESPACE, new CustomDataProvider()); + + ProviderManager.addExtensionProvider(ForwardComment.ELEMENT, + ForwardComment.NAMESPACE, new ForwardCommentProvider()); + + ProviderManager.addExtensionProvider(ReferenceElement.ELEMENT, + ReferenceElement.NAMESPACE, new ReferencesProvider()); + try { LogManager.i(this, "Trying to connect and login..."); if (!connection.isConnected()) { @@ -141,13 +152,6 @@ void connectAndLogin() { connection.login(); - // can be a cause of strange Smack behavior - // not authorization or not receiving a iq's - ProviderManager.addExtensionProvider(DataForm.ELEMENT, - DataForm.NAMESPACE, new CustomDataProvider()); - - ProviderManager.addExtensionProvider(ForwardComment.ELEMENT, - ForwardComment.NAMESPACE, new ForwardCommentProvider()); } else { LogManager.i(this, "Already authenticated"); } diff --git a/xabber/src/main/java/com/xabber/android/data/connection/ReconnectionManager.java b/xabber/src/main/java/com/xabber/android/data/connection/ReconnectionManager.java index 7bb5c4562d..26ce4f77d9 100644 --- a/xabber/src/main/java/com/xabber/android/data/connection/ReconnectionManager.java +++ b/xabber/src/main/java/com/xabber/android/data/connection/ReconnectionManager.java @@ -9,6 +9,7 @@ import com.xabber.android.data.connection.listeners.OnConnectedListener; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.log.LogManager; +import com.xabber.android.data.push.SyncManager; import java.util.Collection; import java.util.HashMap; @@ -96,7 +97,8 @@ private void checkConnection(AccountItem accountItem, ReconnectionInfo reconnect private boolean isAccountNeedConnection(AccountItem accountItem) { return accountItem.isEnabled() && accountItem.getRawStatusMode().isOnline() - && !accountItem.getConnection().isAuthenticated(); + && !accountItem.getConnection().isAuthenticated() + && SyncManager.getInstance().isAccountNeedConnection(accountItem); } private boolean isTimeToReconnect(ReconnectionInfo reconnectionInfo) { diff --git a/xabber/src/main/java/com/xabber/android/data/database/MessageDatabaseManager.java b/xabber/src/main/java/com/xabber/android/data/database/MessageDatabaseManager.java index 3575aad815..710873f6b8 100644 --- a/xabber/src/main/java/com/xabber/android/data/database/MessageDatabaseManager.java +++ b/xabber/src/main/java/com/xabber/android/data/database/MessageDatabaseManager.java @@ -9,6 +9,8 @@ import com.xabber.android.data.database.messagerealm.ForwardId; import com.xabber.android.data.database.messagerealm.MessageItem; import com.xabber.android.data.database.messagerealm.SyncInfo; +import com.xabber.android.data.database.realm.ContactGroup; +import com.xabber.android.data.database.realm.ContactRealm; import com.xabber.android.data.database.sqlite.MessageTable; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.entity.UserJid; @@ -16,6 +18,7 @@ import org.jxmpp.stringprep.XmppStringprepException; +import java.lang.reflect.Field; import java.util.Date; import io.realm.DynamicRealm; @@ -33,7 +36,7 @@ public class MessageDatabaseManager { private static final String REALM_MESSAGE_DATABASE_NAME = "xabber.realm"; - static final int REALM_MESSAGE_DATABASE_VERSION = 19; + static final int REALM_MESSAGE_DATABASE_VERSION = 22; private final RealmConfiguration realmConfiguration; private static MessageDatabaseManager instance; @@ -137,7 +140,8 @@ public void execute(Realm realm) { } - @RealmModule(classes = {MessageItem.class, SyncInfo.class, Attachment.class, ForwardId.class}) + @RealmModule(classes = {MessageItem.class, SyncInfo.class, Attachment.class, ForwardId.class, + ContactRealm.class, ContactGroup.class}) static class MessageRealmDatabaseModule { } @@ -320,6 +324,42 @@ public void apply(DynamicRealmObject obj) { oldVersion++; } + if (oldVersion == 19) { + schema.get(MessageItem.class.getSimpleName()) + .addField(MessageItem.Fields.PREVIOUS_ID, String.class) + .addField(MessageItem.Fields.ARCHIVED_ID, String.class) + .transform(new RealmObjectSchema.Function() { + @Override + public void apply(DynamicRealmObject obj) { + obj.setString(MessageItem.Fields.PREVIOUS_ID, "legacy"); + } + }); + oldVersion++; + } + + if (oldVersion == 20) { + schema.create(ContactGroup.class.getSimpleName()) + .addField(ContactGroup.Fields.GROUP_NAME, String.class, + FieldAttribute.PRIMARY_KEY, FieldAttribute.REQUIRED); + + schema.create(ContactRealm.class.getSimpleName()) + .addField(ContactRealm.Fields.ID, String.class, FieldAttribute.PRIMARY_KEY, FieldAttribute.REQUIRED) + .addField(ContactRealm.Fields.ACCOUNT, String.class) + .addField(ContactRealm.Fields.USER, String.class) + .addField(ContactRealm.Fields.NAME, String.class) + .addField(ContactRealm.Fields.ACCOUNT_RESOURCE, String.class) + .addRealmObjectField(ContactRealm.Fields.LAST_MESSAGE, schema.get(MessageItem.class.getSimpleName())) + .addRealmListField(ContactRealm.Fields.GROUPS, schema.get(ContactGroup.class.getSimpleName())); + + oldVersion++; + } + + if (oldVersion == 21) { + schema.get(MessageItem.class.getSimpleName()) + .addField(MessageItem.Fields.MARKUP_TEXT, String.class); + oldVersion++; + } + } }) .build(); diff --git a/xabber/src/main/java/com/xabber/android/data/database/RealmManager.java b/xabber/src/main/java/com/xabber/android/data/database/RealmManager.java index 50de97fb6e..928bb5d032 100644 --- a/xabber/src/main/java/com/xabber/android/data/database/RealmManager.java +++ b/xabber/src/main/java/com/xabber/android/data/database/RealmManager.java @@ -14,6 +14,7 @@ import com.xabber.android.data.database.realm.NotificationStateRealm; import com.xabber.android.data.database.realm.PatreonGoalRealm; import com.xabber.android.data.database.realm.PatreonRealm; +import com.xabber.android.data.database.realm.PushLogRecord; import com.xabber.android.data.database.realm.SocialBindingRealm; import com.xabber.android.data.database.realm.SyncStateRealm; import com.xabber.android.data.database.realm.XMPPUserRealm; @@ -24,6 +25,7 @@ import com.xabber.android.data.notification.custom_notification.NotifyPrefsRealm; import io.realm.DynamicRealm; +import io.realm.DynamicRealmObject; import io.realm.FieldAttribute; import io.realm.Realm; import io.realm.RealmConfiguration; @@ -34,7 +36,7 @@ public class RealmManager { private static final String REALM_DATABASE_NAME = "realm_database.realm"; - private static final int REALM_DATABASE_VERSION = 21; + private static final int REALM_DATABASE_VERSION = 27; private static final String LOG_TAG = RealmManager.class.getSimpleName(); private final RealmConfiguration realmConfiguration; @@ -69,7 +71,7 @@ void deleteRealm() { XMPPUserRealm.class, EmailRealm.class, SocialBindingRealm.class, SyncStateRealm.class, PatreonGoalRealm.class, PatreonRealm.class, ChatDataRealm.class, NotificationStateRealm.class, CrowdfundingMessage.class, NotifChatRealm.class, NotifMessageRealm.class, NotifyPrefsRealm.class, - UploadServer.class}) + UploadServer.class, PushLogRecord.class}) static class RealmDatabaseModule { } @@ -311,6 +313,51 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { oldVersion++; } + + if (oldVersion == 21) { + schema.get(AccountRealm.class.getSimpleName()) + .addField(AccountRealm.Fields.PUSH_NODE, String.class); + + oldVersion++; + } + + if (oldVersion == 22) { + schema.get(AccountRealm.class.getSimpleName()) + .addField(AccountRealm.Fields.PUSH_ENABLED, boolean.class) + .addField(AccountRealm.Fields.PUSH_WAS_ENABLED, boolean.class); + + oldVersion++; + } + + if (oldVersion == 23) { + schema.get(AccountRealm.class.getSimpleName()) + .addField(AccountRealm.Fields.PUSH_SERVICE_JID, String.class); + + oldVersion++; + } + + if (oldVersion == 24) { + schema.create(PushLogRecord.class.getSimpleName()) + .addField(UploadServer.Fields.ID, String.class, FieldAttribute.PRIMARY_KEY, FieldAttribute.REQUIRED) + .addField(PushLogRecord.Fields.TIME, long.class) + .addField(PushLogRecord.Fields.MESSAGE, String.class); + + oldVersion++; + } + + if (oldVersion == 25) { + schema.get(ChatDataRealm.class.getSimpleName()) + .addField("historyRequestedAtStart", boolean.class); + + oldVersion++; + } + + if (oldVersion == 26) { + schema.get(CrowdfundingMessage.class.getSimpleName()) + .removeField("receivedTimestamp"); + + oldVersion++; + } } }) .modules(new RealmDatabaseModule()) diff --git a/xabber/src/main/java/com/xabber/android/data/database/messagerealm/MessageItem.java b/xabber/src/main/java/com/xabber/android/data/database/messagerealm/MessageItem.java index b9fddff349..96e44ea745 100644 --- a/xabber/src/main/java/com/xabber/android/data/database/messagerealm/MessageItem.java +++ b/xabber/src/main/java/com/xabber/android/data/database/messagerealm/MessageItem.java @@ -32,6 +32,7 @@ import io.realm.RealmList; import io.realm.RealmObject; +import io.realm.annotations.Ignore; import io.realm.annotations.Index; import io.realm.annotations.PrimaryKey; import io.realm.annotations.Required; @@ -44,6 +45,7 @@ public static class Fields { public static final String USER = "user"; public static final String RESOURCE = "resource"; public static final String TEXT = "text"; + public static final String MARKUP_TEXT = "markupText"; public static final String ACTION = "action"; public static final String INCOMING = "incoming"; public static final String ENCRYPTED = "encrypted"; @@ -74,6 +76,8 @@ public static class Fields { public static final String ORIGINAL_FROM = "originalFrom"; public static final String PARENT_MESSAGE_ID = "parentMessageId"; public static final String FROM_MUC = "fromMUC"; + public static final String PREVIOUS_ID = "previousId"; + public static final String ARCHIVED_ID = "archivedId"; } /** @@ -97,6 +101,7 @@ public static class Fields { * Text representation. */ private String text; + private String markupText; /** * Optional action. If set message represent not an actual message but some * action in the chat. @@ -209,6 +214,10 @@ public static class Fields { private String originalFrom; private String parentMessageId; + private String previousId; + private String archivedId; + @Ignore + private String packetId; private RealmList forwardedIds; @@ -555,6 +564,30 @@ public void setParentMessageId(String parentMessageId) { this.parentMessageId = parentMessageId; } + public String getPreviousId() { + return previousId; + } + + public void setPreviousId(String previousId) { + this.previousId = previousId; + } + + public String getArchivedId() { + return archivedId; + } + + public void setArchivedId(String archivedId) { + this.archivedId = archivedId; + } + + public String getPacketId() { + return packetId; + } + + public void setPacketId(String packetId) { + this.packetId = packetId; + } + public boolean isFromMUC() { return fromMUC; } @@ -562,4 +595,12 @@ public boolean isFromMUC() { public void setFromMUC(boolean fromMUC) { this.fromMUC = fromMUC; } + + public String getMarkupText() { + return markupText; + } + + public void setMarkupText(String markupText) { + this.markupText = markupText; + } } diff --git a/xabber/src/main/java/com/xabber/android/data/database/realm/AccountRealm.java b/xabber/src/main/java/com/xabber/android/data/database/realm/AccountRealm.java index 2fdf0bc842..f4f67b2b49 100644 --- a/xabber/src/main/java/com/xabber/android/data/database/realm/AccountRealm.java +++ b/xabber/src/main/java/com/xabber/android/data/database/realm/AccountRealm.java @@ -35,6 +35,10 @@ public static class Fields { public static final String MAM_DEFAULT_BEHAVIOR = "mamDefaultBehavior"; public static final String LOAD_HISTORY_SETTINGS = "loadHistorySettings"; public static final String SUCCESSFUL_CONNECTION_HAPPENED = "successfulConnectionHappened"; + public static final String PUSH_NODE = "pushNode"; + public static final String PUSH_SERVICE_JID = "pushServiceJid"; + public static final String PUSH_ENABLED = "pushEnabled"; + public static final String PUSH_WAS_ENABLED = "pushWasEnabled"; } @PrimaryKey @@ -93,6 +97,11 @@ public static class Fields { */ private boolean successfulConnectionHappened; + private String pushNode; + private String pushServiceJid; + private boolean pushEnabled; + private boolean pushWasEnabled; + public AccountRealm(String id) { this.id = id; } @@ -415,4 +424,36 @@ public boolean isSuccessfulConnectionHappened() { public void setSuccessfulConnectionHappened(boolean successfulConnectionHappened) { this.successfulConnectionHappened = successfulConnectionHappened; } + + public String getPushNode() { + return pushNode; + } + + public void setPushNode(String pushNode) { + this.pushNode = pushNode; + } + + public String getPushServiceJid() { + return pushServiceJid; + } + + public void setPushServiceJid(String pushServiceJid) { + this.pushServiceJid = pushServiceJid; + } + + public boolean isPushEnabled() { + return pushEnabled; + } + + public void setPushEnabled(boolean pushEnabled) { + this.pushEnabled = pushEnabled; + } + + public boolean isPushWasEnabled() { + return pushWasEnabled; + } + + public void setPushWasEnabled(boolean pushWasEnabled) { + this.pushWasEnabled = pushWasEnabled; + } } diff --git a/xabber/src/main/java/com/xabber/android/data/database/realm/ChatDataRealm.java b/xabber/src/main/java/com/xabber/android/data/database/realm/ChatDataRealm.java index a0a7c0e116..6c73e6e09f 100644 --- a/xabber/src/main/java/com/xabber/android/data/database/realm/ChatDataRealm.java +++ b/xabber/src/main/java/com/xabber/android/data/database/realm/ChatDataRealm.java @@ -23,6 +23,7 @@ public class ChatDataRealm extends RealmObject { private boolean archived; private NotificationStateRealm notificationState; private int lastPosition; + private boolean historyRequestedAtStart; public ChatDataRealm(String accountJid, String userJid) { this.id = accountJid + "-" + userJid; @@ -89,4 +90,12 @@ public int getLastPosition() { public void setLastPosition(int lastPosition) { this.lastPosition = lastPosition; } + + public boolean isHistoryRequestedAtStart() { + return historyRequestedAtStart; + } + + public void setHistoryRequestedAtStart(boolean historyRequestedAtStart) { + this.historyRequestedAtStart = historyRequestedAtStart; + } } diff --git a/xabber/src/main/java/com/xabber/android/data/database/realm/ContactGroup.java b/xabber/src/main/java/com/xabber/android/data/database/realm/ContactGroup.java new file mode 100644 index 0000000000..0441126796 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/database/realm/ContactGroup.java @@ -0,0 +1,30 @@ +package com.xabber.android.data.database.realm; + +import java.util.UUID; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.Required; + +public class ContactGroup extends RealmObject { + + public static class Fields { + public static final String GROUP_NAME = "groupName"; + } + + @PrimaryKey + @Required + private String groupName; + + public ContactGroup() { + this.groupName = UUID.randomUUID().toString(); + } + + public ContactGroup(String groupName) { + this.groupName = groupName; + } + + public String getGroupName() { + return groupName; + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/database/realm/ContactRealm.java b/xabber/src/main/java/com/xabber/android/data/database/realm/ContactRealm.java new file mode 100644 index 0000000000..3f353dff72 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/database/realm/ContactRealm.java @@ -0,0 +1,94 @@ +package com.xabber.android.data.database.realm; + +import com.xabber.android.data.database.messagerealm.MessageItem; + +import java.util.UUID; + +import io.realm.RealmList; +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.Required; + +public class ContactRealm extends RealmObject { + + public static class Fields { + public static final String ID = "id"; + public static final String ACCOUNT = "account"; + public static final String USER = "user"; + public static final String NAME = "name"; + public static final String ACCOUNT_RESOURCE = "accountResource"; + public static final String LAST_MESSAGE = "lastMessage"; + public static final String GROUPS = "groups"; + } + + @PrimaryKey + @Required + private String id; + + private String account; + private String user; + private String accountResource; + private String name; + private MessageItem lastMessage; + private RealmList groups; + + public ContactRealm() { + this.id = UUID.randomUUID().toString(); + } + + public ContactRealm(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAccountResource() { + return accountResource; + } + + public void setAccountResource(String accountResource) { + this.accountResource = accountResource; + } + + public MessageItem getLastMessage() { + return lastMessage; + } + + public void setLastMessage(MessageItem lastMessage) { + this.lastMessage = lastMessage; + } + + public RealmList getGroups() { + return groups; + } + + public void setGroups(RealmList groups) { + this.groups = groups; + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/database/realm/CrowdfundingMessage.java b/xabber/src/main/java/com/xabber/android/data/database/realm/CrowdfundingMessage.java index 3d04be60ed..af50e10692 100644 --- a/xabber/src/main/java/com/xabber/android/data/database/realm/CrowdfundingMessage.java +++ b/xabber/src/main/java/com/xabber/android/data/database/realm/CrowdfundingMessage.java @@ -11,12 +11,18 @@ public class CrowdfundingMessage extends RealmObject { + public static class Fields { + public static final String ID = "id"; + public static final String TIMESTAMP = "timestamp"; + public static final String READ = "read"; + public static final String DELAY = "delay"; + } + @PrimaryKey @Required private String id; private boolean isLeader; private int timestamp; - private int receivedTimestamp; private String messageRu; private String messageEn; private boolean read; @@ -119,14 +125,6 @@ public void setDelay(int delay) { this.delay = delay; } - public int getReceivedTimestamp() { - return receivedTimestamp; - } - - public void setReceivedTimestamp(int receivedTimestamp) { - this.receivedTimestamp = receivedTimestamp; - } - public String getNameForCurrentLocale() { Locale currentLocale = Application.getInstance().getResources().getConfiguration().locale; if (currentLocale.getLanguage().equals("ru") && getAuthorNameRu() != null) diff --git a/xabber/src/main/java/com/xabber/android/data/database/realm/PushLogRecord.java b/xabber/src/main/java/com/xabber/android/data/database/realm/PushLogRecord.java new file mode 100644 index 0000000000..e2ded51021 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/database/realm/PushLogRecord.java @@ -0,0 +1,48 @@ +package com.xabber.android.data.database.realm; + +import java.util.UUID; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import io.realm.annotations.Required; + +public class PushLogRecord extends RealmObject { + + public static class Fields { + public static final String ID = "id"; + public static final String TIME = "time"; + public static final String MESSAGE = "message"; + } + + @PrimaryKey + @Required + private String id; + private long time; + private String message; + + public PushLogRecord(long time, String message) { + this.id = UUID.randomUUID().toString(); + this.time = time; + this.message = message; + } + + public PushLogRecord() { + this.id = UUID.randomUUID().toString(); + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public long getTime() { + return time; + } + + public void setTime(long time) { + this.time = time; + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/database/sqlite/AccountTable.java b/xabber/src/main/java/com/xabber/android/data/database/sqlite/AccountTable.java index fc34974e78..8493491e0a 100644 --- a/xabber/src/main/java/com/xabber/android/data/database/sqlite/AccountTable.java +++ b/xabber/src/main/java/com/xabber/android/data/database/sqlite/AccountTable.java @@ -483,6 +483,10 @@ private void saveAccountRealm(String id, AccountItem accountItem) { accountRealm.setMamDefaultBehavior(accountItem.getMamDefaultBehaviour()); accountRealm.setLoadHistorySettings(accountItem.getLoadHistorySettings()); accountRealm.setSuccessfulConnectionHappened(accountItem.isSuccessfulConnectionHappened()); + accountRealm.setPushNode(accountItem.getPushNode()); + accountRealm.setPushServiceJid(accountItem.getPushServiceJid()); + accountRealm.setPushEnabled(accountItem.isPushEnabled()); + accountRealm.setPushWasEnabled(accountItem.isPushWasEnabled()); Realm realm = RealmManager.getInstance().getNewBackgroundRealm(); realm.beginTransaction(); diff --git a/xabber/src/main/java/com/xabber/android/data/entity/NestedMap.java b/xabber/src/main/java/com/xabber/android/data/entity/NestedMap.java index b2b354b68a..8ae2e2f84b 100644 --- a/xabber/src/main/java/com/xabber/android/data/entity/NestedMap.java +++ b/xabber/src/main/java/com/xabber/android/data/entity/NestedMap.java @@ -20,6 +20,7 @@ import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; /** * Map of map with string value as keys for both maps. @@ -130,6 +131,10 @@ public void addAll(NestedMap nestedMap) { put(entry.getFirst(), entry.getSecond(), entry.getValue()); } + public Set keySet() { + return map.keySet(); + } + /** * Entry stored in {@link NestedMap}. * diff --git a/xabber/src/main/java/com/xabber/android/data/extension/avatar/AvatarManager.java b/xabber/src/main/java/com/xabber/android/data/extension/avatar/AvatarManager.java index 66543a933e..90c0f88180 100644 --- a/xabber/src/main/java/com/xabber/android/data/extension/avatar/AvatarManager.java +++ b/xabber/src/main/java/com/xabber/android/data/extension/avatar/AvatarManager.java @@ -18,12 +18,10 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -47,6 +45,8 @@ import com.xabber.android.data.entity.UserJid; import com.xabber.android.data.extension.vcard.VCardManager; import com.xabber.android.data.log.LogManager; +import com.xabber.android.data.roster.OnContactChangedListener; +import com.xabber.android.data.roster.RosterContact; import com.xabber.android.ui.color.ColorManager; import com.xabber.xmpp.vcardupdate.VCardUpdate; @@ -57,6 +57,7 @@ import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.stringprep.XmppStringprepException; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -105,14 +106,7 @@ public class AvatarManager implements OnLoadListener, OnLowMemoryListener, OnPac * Map with drawable used in contact list only for specified uses. */ private final Map contactListDrawables; - /** - * Users' default avatar set. - */ - private final BaseAvatarSet userAvatarSet; - /** - * Rooms' default avatar set. - */ - private final BaseAvatarSet roomAvatarSet; + private final Map contactListDefaultDrawables; public static AvatarManager getInstance() { if (instance == null) { @@ -124,12 +118,11 @@ public static AvatarManager getInstance() { private AvatarManager() { this.application = Application.getInstance(); - userAvatarSet = new BaseAvatarSet(application, R.array.default_avatars_icons, R.array.default_avatars_colors); - roomAvatarSet = new BaseAvatarSet(application, R.array.muc_avatars, R.array.default_avatars_colors); hashes = new HashMap<>(); bitmaps = new HashMap<>(); contactListDrawables = new HashMap<>(); + contactListDefaultDrawables = new HashMap<>(); } /** @@ -184,23 +177,27 @@ public static Bitmap drawableToBitmap(Drawable drawable) { } public static Bitmap getCircleBitmap(Bitmap bitmap) { - final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), - bitmap.getHeight(), Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(output); + if (bitmap.getWidth() != bitmap.getHeight()) { + int min = Math.min(bitmap.getWidth(), bitmap.getHeight()); + int max = Math.max(bitmap.getWidth(), bitmap.getHeight()); + int x = bitmap.getWidth() > min ? ((max - min) / 2) : 0; + int y = bitmap.getHeight() > min ? ((max - min) / 2) : 0; + bitmap = Bitmap.createBitmap(bitmap, x, y, min, min); + } + final int size = bitmap.getWidth(); + final Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); - final int color = Color.RED; + final Canvas canvas = new Canvas(output); final Paint paint = new Paint(); - final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); - final RectF rectF = new RectF(rect); + final Rect rect = new Rect(0, 0, size, size); + final float r = size / 2; paint.setAntiAlias(true); canvas.drawARGB(0, 0, 0, 0); - paint.setColor(color); - canvas.drawOval(rectF, paint); - + paint.setColor(0xff424242); + canvas.drawCircle(r, r, r, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(bitmap, rect, rect, paint); - return output; } @@ -241,6 +238,10 @@ public void run() { private void onLoaded(Map hashes, Map bitmaps) { this.hashes.putAll(hashes); this.bitmaps.putAll(bitmaps); + for (OnContactChangedListener onContactChangedListener : Application + .getInstance().getUIListeners(OnContactChangedListener.class)) { + onContactChangedListener.onContactsChanged(Collections.emptyList()); + } } /** @@ -252,6 +253,7 @@ private void onLoaded(Map hashes, Map bitmaps) { private void setHash(final Jid jid, final String hash) { hashes.put(jid, hash == null ? EMPTY_HASH : hash); contactListDrawables.remove(jid); + contactListDefaultDrawables.remove(jid); application.runInBackground(new Runnable() { @Override public void run() { @@ -308,8 +310,7 @@ public void run() { @Override public void onLowMemory() { contactListDrawables.clear(); - userAvatarSet.onLowMemory(); - roomAvatarSet.onLowMemory(); + contactListDefaultDrawables.clear(); } /** @@ -352,128 +353,121 @@ public Drawable getDefaultAccountAvatarForSync(AccountJid account, int color) { return generateDefaultAvatar(account.getFullJid().asBareJid().toString(), name, color); } - /** - * Gets avatar for regular user. - * - * @param user - * @return - */ - public Drawable getUserAvatar(UserJid user, String name) { - Bitmap value = getBitmap(user.getJid()); - if (value != null) { - return new BitmapDrawable(application.getResources(), value); - } else { - return generateDefaultAvatar(user.getBareJid().toString(), name); + /** Gets and caches drawable with avatar for regular user. + * Or generate and caches text-based avatar. */ + public Drawable getUserAvatarForContactList(UserJid user, String name) { + Drawable drawable = contactListDrawables.get(user.getJid()); + if (drawable == null) { + drawable = getUserAvatar(user); + if (drawable != null) { + contactListDrawables.put(user.getJid(), drawable); + contactListDefaultDrawables.remove(user.getJid()); + return drawable; + } else { + return getDefaultAvatar(user, name); + } } + return drawable; } - private Drawable getDefaultAvatarDrawable(BaseAvatarSet.DefaultAvatar defaultAvatar) { - Drawable[] layers = new Drawable[2]; - layers[0] = new ColorDrawable(defaultAvatar.getBackgroundColor()); - layers[1] = application.getResources().getDrawable(defaultAvatar.getIconResource()); - - - return new LayerDrawable(layers); + /** Gets and caches drawable with room's avatar. + * Or generate and caches text-based avatar. */ + public Drawable getRoomAvatarForContactList(UserJid user) { + Drawable drawable = contactListDrawables.get(user.getJid()); + if (drawable == null) { + drawable = getRoomAvatar(user); + if (drawable != null) { + contactListDrawables.put(user.getJid(), drawable); + contactListDefaultDrawables.remove(user.getJid()); + return drawable; + } else { + return getDefaultRoomAvatar(user); + } + } + return drawable; } - public Drawable generateDefaultRoomAvatar(@NonNull String jid) { - Drawable[] layers = new Drawable[2]; - layers[0] = new ColorDrawable(ColorGenerator.MATERIAL.getColor(jid)); - layers[1] = application.getResources().getDrawable(R.drawable.ic_conference_white); - - LayerDrawable layerDrawable = new LayerDrawable(layers); - layerDrawable.setLayerInset(1, 25, 25, 25, 30); + /** Gets bitmap with avatar for regular user. */ + public Bitmap getUserBitmap(UserJid user, String name) { + return getCircleBitmap(drawableToBitmap(getUserAvatarForContactList(user, name))); + } - return layerDrawable; + /** Gets bitmap with avatar for room. */ + public Bitmap getRoomBitmap(UserJid user) { + return getCircleBitmap(drawableToBitmap(getRoomAvatarForContactList(user))); } + /** Generate text-based avatar for regular user. */ public Drawable generateDefaultAvatar(@NonNull String jid, @NonNull String name) { return generateDefaultAvatar(jid, name, ColorGenerator.MATERIAL.getColor(jid)); } - public Drawable generateDefaultAvatar(@NonNull String jid, @NonNull String name, int color) { - String[] words = name.split("\\s+"); - String chars = ""; - - if (words.length >= 1 && words[0].length() > 0) - chars = chars + words[0].substring(0, 1); - - if (words.length >= 2 && words[1].length() > 0) - chars = chars + words[1].substring(0, 1); + /** PRIVATE */ - return TextDrawable.builder() - .beginConfig().fontSize(60).bold().width(150).height(150).endConfig() - .buildRound(chars.toUpperCase(), color); + /** Gets avatar drawable for regular user from bitmap. */ + private Drawable getUserAvatar(UserJid user) { + Bitmap value = getBitmap(user.getJid()); + if (value != null) { + return new BitmapDrawable(application.getResources(), value); + } + return null; } - /** - * Gets bitmap with avatar for regular user. - * - * @param user - * @return - */ - public Bitmap getUserBitmap(UserJid user, String name) { + /** Gets avatar drawable for room from bitmap. */ + private Drawable getRoomAvatar(UserJid user) { Bitmap value = getBitmap(user.getJid()); if (value != null) { - return getCircleBitmap(value); - } else { - return drawableToBitmap(generateDefaultAvatar(user.getBareJid().toString(), name)); + return new BitmapDrawable(application.getResources(), value); } + return null; } - /** - * Gets and caches drawable with avatar for regular user. - * - * @param user - * @return - */ - public Drawable getUserAvatarForContactList(UserJid user, String name) { - Drawable drawable = contactListDrawables.get(user.getJid()); + /** Gets and caches text-base avatar for regular user from cached drawables. */ + private Drawable getDefaultAvatar(UserJid user, String name) { + Drawable drawable = contactListDefaultDrawables.get(user.getJid()); if (drawable == null) { - drawable = getUserAvatar(user, name); - contactListDrawables.put(user.getJid(), drawable); + drawable = generateDefaultAvatar(user.getBareJid().toString(), name); + contactListDefaultDrawables.put(user.getJid(), drawable); } return drawable; } - /** - * Gets avatar for the room. - * - * @param user - * @return - */ - public Drawable getRoomAvatar(UserJid user) { - Bitmap value = getBitmap(user.getJid()); - if (value != null) { - return new BitmapDrawable(application.getResources(), value); - } else { - return generateDefaultRoomAvatar(user.getBareJid().toString()); + /** Gets and caches text-base avatar for room from cached drawables. */ + private Drawable getDefaultRoomAvatar(UserJid user) { + Drawable drawable = contactListDefaultDrawables.get(user.getJid()); + if (drawable == null) { + drawable = generateDefaultRoomAvatar(user.getBareJid().toString()); + contactListDefaultDrawables.put(user.getJid(), drawable); } + return drawable; } - /** - * Gets bitmap for the room. - * - * @param user - * @return - */ - public Bitmap getRoomBitmap(UserJid user) { - return drawableToBitmap(getRoomAvatar(user)); + /** Generate text-based avatar for regular user. */ + private Drawable generateDefaultAvatar(@NonNull String jid, @NonNull String name, int color) { + String[] words = name.split("\\s+"); + String chars = ""; + + if (words.length >= 1 && words[0].length() > 0) + chars = chars + words[0].substring(0, 1); + + if (words.length >= 2 && words[1].length() > 0) + chars = chars + words[1].substring(0, 1); + + return TextDrawable.builder() + .beginConfig().fontSize(60).bold().width(150).height(150).endConfig() + .buildRound(chars.toUpperCase(), color); } - /** - * Gets and caches drawable with room's avatar. - * - * @param user - * @return - */ - public Drawable getRoomAvatarForContactList(UserJid user) { - Drawable drawable = contactListDrawables.get(user.getJid()); - if (drawable == null) { - drawable = getRoomAvatar(user); - contactListDrawables.put(user.getJid(), drawable); - } - return drawable; + /** Generate text-based avatar for room. */ + private Drawable generateDefaultRoomAvatar(@NonNull String jid) { + Drawable[] layers = new Drawable[2]; + layers[0] = new ColorDrawable(ColorGenerator.MATERIAL.getColor(jid)); + layers[1] = application.getResources().getDrawable(R.drawable.ic_conference_white); + + LayerDrawable layerDrawable = new LayerDrawable(layers); + layerDrawable.setLayerInset(1, 25, 25, 25, 30); + + return layerDrawable; } /** diff --git a/xabber/src/main/java/com/xabber/android/data/extension/avatar/AvatarStorage.java b/xabber/src/main/java/com/xabber/android/data/extension/avatar/AvatarStorage.java index c15f1672bc..a29aae01a9 100644 --- a/xabber/src/main/java/com/xabber/android/data/extension/avatar/AvatarStorage.java +++ b/xabber/src/main/java/com/xabber/android/data/extension/avatar/AvatarStorage.java @@ -62,6 +62,7 @@ byte[] read(String hash) { byte[] value; FileInputStream inputStream; try { + if (!getFile(hash).exists()) return null; inputStream = new FileInputStream(getFile(hash)); value = new byte[inputStream.available()]; inputStream.read(value); diff --git a/xabber/src/main/java/com/xabber/android/data/extension/bookmarks/BookmarksManager.java b/xabber/src/main/java/com/xabber/android/data/extension/bookmarks/BookmarksManager.java index cd4517dd41..ee7ee45398 100644 --- a/xabber/src/main/java/com/xabber/android/data/extension/bookmarks/BookmarksManager.java +++ b/xabber/src/main/java/com/xabber/android/data/extension/bookmarks/BookmarksManager.java @@ -3,6 +3,7 @@ import android.support.annotation.NonNull; import com.xabber.android.data.Application; +import com.xabber.android.data.SettingsManager; import com.xabber.android.data.account.AccountItem; import com.xabber.android.data.account.AccountManager; import com.xabber.android.data.entity.AccountJid; @@ -19,9 +20,7 @@ import org.jivesoftware.smackx.bookmarks.BookmarkManager; import org.jivesoftware.smackx.bookmarks.BookmarkedConference; import org.jivesoftware.smackx.bookmarks.BookmarkedURL; -import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; -import org.jxmpp.jid.Jid; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.stringprep.XmppStringprepException; @@ -31,7 +30,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Set; /** * Manage bookmarks and there requests. @@ -184,6 +182,8 @@ public void cleanCache(AccountJid accountJid) { } public void onAuthorized(AccountJid account) { + if (!SettingsManager.syncBookmarksOnStart()) return; + cleanCache(account); List conferences; diff --git a/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/ChatMarkerManager.java b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/ChatMarkerManager.java index 3f91ce0874..4652bd078f 100644 --- a/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/ChatMarkerManager.java +++ b/xabber/src/main/java/com/xabber/android/data/extension/chat_markers/ChatMarkerManager.java @@ -1,5 +1,6 @@ package com.xabber.android.data.extension.chat_markers; +import com.xabber.android.data.Application; import com.xabber.android.data.NetworkException; import com.xabber.android.data.account.AccountItem; import com.xabber.android.data.account.AccountManager; @@ -102,11 +103,8 @@ public void sendDisplayed(MessageItem messageItem) { Message displayed = new Message(messageItem.getUser().getJid()); displayed.addExtension(new ChatMarkersElements.DisplayedExtension(messageItem.getStanzaId())); displayed.setType(Message.Type.chat); - try { - StanzaSender.sendStanza(messageItem.getAccount(), displayed); - } catch (NetworkException e) { - LogManager.exception(this, e); - } + + sendMessageInBackgroundUserRequest(displayed, messageItem.getAccount()); } public void processCarbonsMessage(AccountJid account, final Message message, CarbonExtension.Direction direction) { @@ -164,11 +162,20 @@ private void sendReceived(Message message, AccountJid account) { received.setThread(message.getThread()); received.setType(Message.Type.chat); - try { - StanzaSender.sendStanza(account, received); - } catch (NetworkException e) { - LogManager.exception(this, e); - } + sendMessageInBackgroundUserRequest(received, account); + } + + private void sendMessageInBackgroundUserRequest(final Message message, final AccountJid account) { + Application.getInstance().runInBackgroundUserRequest(new Runnable() { + @Override + public void run() { + try { + StanzaSender.sendStanza(account, message); + } catch (NetworkException e) { + LogManager.exception(this, e); + } + } + }); } private void markAsDisplayed(final String messageID) { @@ -198,15 +205,26 @@ private void markAsDisplayed(final String messageID) { private void markAsDelivered(final String stanzaID) { Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread(); - MessageItem first = realm.where(MessageItem.class) .equalTo(MessageItem.Fields.STANZA_ID, stanzaID).findFirst(); if (first != null) { - realm.beginTransaction(); - first.setDelivered(true); - realm.commitTransaction(); + RealmResults results = realm.where(MessageItem.class) + .equalTo(MessageItem.Fields.ACCOUNT, first.getAccount().toString()) + .equalTo(MessageItem.Fields.USER, first.getUser().toString()) + .equalTo(MessageItem.Fields.INCOMING, false) + .equalTo(MessageItem.Fields.DELIVERED, false) + .lessThanOrEqualTo(MessageItem.Fields.TIMESTAMP, first.getTimestamp()) + .findAll(); + + if (results != null) { + realm.beginTransaction(); + for (MessageItem item : results) { + item.setDelivered(true); + } + realm.commitTransaction(); + EventBus.getDefault().post(new MessageUpdateEvent()); + } } - EventBus.getDefault().post(new MessageUpdateEvent()); } } diff --git a/xabber/src/main/java/com/xabber/android/data/extension/httpfileupload/HttpFileUploadManager.java b/xabber/src/main/java/com/xabber/android/data/extension/httpfileupload/HttpFileUploadManager.java index 4f0a054951..9eb14c80fd 100644 --- a/xabber/src/main/java/com/xabber/android/data/extension/httpfileupload/HttpFileUploadManager.java +++ b/xabber/src/main/java/com/xabber/android/data/extension/httpfileupload/HttpFileUploadManager.java @@ -22,6 +22,9 @@ import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.entity.UserJid; import com.xabber.android.data.extension.file.FileManager; +import com.xabber.android.data.extension.references.RefFile; +import com.xabber.android.data.extension.references.RefMedia; +import com.xabber.android.data.extension.references.ReferencesManager; import com.xabber.android.data.log.LogManager; import com.xabber.android.data.message.MessageManager; import com.xabber.android.service.UploadService; @@ -224,6 +227,15 @@ public static String getMimeType(String path) { public static RealmList parseFileMessage(Stanza packet) { RealmList attachments = new RealmList<>(); + // parsing data references + List refMediaList = ReferencesManager.getMediaFromReferences(packet); + if (!refMediaList.isEmpty()) { + for (RefMedia media : refMediaList) { + attachments.add(refMediaToAttachment(media)); + } + } + + // parsing data forms DataForm dataForm = DataForm.from(packet); if (dataForm != null) { @@ -238,6 +250,23 @@ public static RealmList parseFileMessage(Stanza packet) { return attachments; } + private static Attachment refMediaToAttachment(RefMedia media) { + Attachment attachment = new Attachment(); + attachment.setFileUrl(media.getUri()); + attachment.setIsImage(FileManager.isImageUrl(media.getUri())); + + RefFile file = media.getFile(); + if (file != null) { + attachment.setTitle(file.getName()); + attachment.setMimeType(file.getMediaType()); + attachment.setDuration(file.getDuration()); + attachment.setFileSize(file.getSize()); + if (file.getHeight() > 0) attachment.setImageHeight(file.getHeight()); + if (file.getWidth() > 0) attachment.setImageWidth(file.getWidth()); + } + return attachment; + } + private static Attachment mediaToAttachment(ExtendedFormField.Media media, String title) { Attachment attachment = new Attachment(); attachment.setTitle(title); diff --git a/xabber/src/main/java/com/xabber/android/data/extension/iqlast/LastActivityInteractor.java b/xabber/src/main/java/com/xabber/android/data/extension/iqlast/LastActivityInteractor.java index 90b6cf6c83..bc61c67583 100644 --- a/xabber/src/main/java/com/xabber/android/data/extension/iqlast/LastActivityInteractor.java +++ b/xabber/src/main/java/com/xabber/android/data/extension/iqlast/LastActivityInteractor.java @@ -1,42 +1,45 @@ package com.xabber.android.data.extension.iqlast; -import com.xabber.android.data.Application; import com.xabber.android.data.account.AccountItem; import com.xabber.android.data.account.AccountManager; +import com.xabber.android.data.connection.ConnectionItem; +import com.xabber.android.data.connection.listeners.OnPacketListener; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.entity.UserJid; +import com.xabber.android.data.log.LogManager; import com.xabber.android.data.roster.RosterManager; import org.jivesoftware.smack.SmackException; -import org.jivesoftware.smack.XMPPException; -import org.jivesoftware.smack.tcp.XMPPTCPConnection; -import org.jivesoftware.smackx.iqlast.LastActivityManager; +import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smackx.iqlast.packet.LastActivity; +import org.jxmpp.jid.Jid; import java.util.HashMap; -import java.util.LinkedList; -public class LastActivityInteractor { +public class LastActivityInteractor implements OnPacketListener { private static LastActivityInteractor instance; private HashMap lastActivities = new HashMap<>(); - private LinkedList queryForLastActivityUpdate = new LinkedList<>(); - private boolean isRun; public static LastActivityInteractor getInstance() { if (instance == null) instance = new LastActivityInteractor(); return instance; } - public void addJidToLastActivityQuery(AccountJid account, UserJid user) { - queryForLastActivityUpdate.addLast(new JidPair(account, user)); - if (!this.isRun) - Application.getInstance().runInBackground(new Runnable() { - @Override - public void run() { - runGettingLastActivity(); + @Override + public void onStanza(ConnectionItem connection, Stanza packet) { + if (packet instanceof LastActivity) { + try { + Jid jid = packet.getFrom(); + long result = ((LastActivity) packet).lastActivity; + if (result > 0) { + result = System.currentTimeMillis() / 1000 - result; + setLastActivity(connection.getAccount(), UserJid.from(jid), result); } - }); + } catch (UserJid.UserJidCreateException e) { + e.printStackTrace(); + } + } } public void setLastActivityTimeNow(AccountJid account, UserJid user) { @@ -50,57 +53,21 @@ public long getLastActivity(UserJid user) { else return 0; } - private void setLastActivity(AccountJid account, UserJid user, long time) { - lastActivities.put(user, time); - RosterManager.onContactChanged(account, user); - } - - private synchronized void runGettingLastActivity() { - this.isRun = true; - while (!queryForLastActivityUpdate.isEmpty()) { - JidPair item = queryForLastActivityUpdate.removeFirst(); - long lastActivity = requestLastActivity(item.account, item.user); - setLastActivity(item.account, item.user, lastActivity); - } - this.isRun = false; - } - - private long requestLastActivity(AccountJid account, UserJid user) { - long lastActivitySeconds = 0; - - AccountItem accountItem = AccountManager - .getInstance().getAccount(account); - - if (accountItem == null) return lastActivitySeconds; - - XMPPTCPConnection xmppConnection = accountItem.getConnection(); - - LastActivityManager lastActivityManager = LastActivityManager.getInstanceFor(xmppConnection); - LastActivity lastActivity = null; - - try { - lastActivity = lastActivityManager.getLastActivity(user.getJid()); - } catch (SmackException.NoResponseException | XMPPException.XMPPErrorException - | SmackException.NotConnectedException | InterruptedException e) { - e.printStackTrace(); + public void requestLastActivityAsync(AccountJid account, UserJid user) { + AccountItem accountItem = AccountManager.getInstance().getAccount(account); + if (accountItem != null) { + LastActivity activity = new LastActivity(user.getJid()); + try { + accountItem.getConnection().sendStanza(activity); + } catch (SmackException.NotConnectedException | InterruptedException e) { + LogManager.d(LastActivityInteractor.class, e.toString()); + } } - - if (lastActivity != null) lastActivitySeconds = lastActivity.lastActivity; - - if (lastActivitySeconds > 0) - lastActivitySeconds = System.currentTimeMillis()/1000 - lastActivitySeconds; - return lastActivitySeconds; } - private class JidPair { - AccountJid account; - UserJid user; - - public JidPair(AccountJid account, UserJid user) { - this.account = account; - this.user = user; - } - + private void setLastActivity(AccountJid account, UserJid user, long time) { + lastActivities.put(user, time); + RosterManager.onContactChanged(account, user); } } diff --git a/xabber/src/main/java/com/xabber/android/data/extension/mam/ArchivedHelper.java b/xabber/src/main/java/com/xabber/android/data/extension/mam/ArchivedHelper.java new file mode 100644 index 0000000000..1da7197396 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/extension/mam/ArchivedHelper.java @@ -0,0 +1,25 @@ +package com.xabber.android.data.extension.mam; + +import org.jivesoftware.smack.packet.StandardExtensionElement; +import org.jivesoftware.smack.packet.Stanza; + +public class ArchivedHelper { + + private final static String ELEMENT_NAME = "archived"; + private final static String NAMESPACE = "urn:xmpp:mam:tmp"; + private final static String ATTRIBUTE_ID = "id"; + private final static String ATTRIBUTE_BY = "by"; + + public static String getArchivedId(Stanza stanza) { + StandardExtensionElement sidElement = stanza.getExtension(ELEMENT_NAME, NAMESPACE); + if (sidElement != null) return sidElement.getAttributeValue(ATTRIBUTE_ID); + else return null; + } + + public static String getArchivedBy(Stanza stanza) { + StandardExtensionElement sidElement = stanza.getExtension(ELEMENT_NAME, NAMESPACE); + if (sidElement != null) return sidElement.getAttributeValue(ATTRIBUTE_BY); + else return null; + } + +} diff --git a/xabber/src/main/java/com/xabber/android/data/extension/mam/MamManager.java b/xabber/src/main/java/com/xabber/android/data/extension/mam/MamManager.java deleted file mode 100644 index f3dd41fe05..0000000000 --- a/xabber/src/main/java/com/xabber/android/data/extension/mam/MamManager.java +++ /dev/null @@ -1,660 +0,0 @@ -package com.xabber.android.data.extension.mam; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Log; - -import com.xabber.android.data.Application; -import com.xabber.android.data.account.AccountItem; -import com.xabber.android.data.account.AccountManager; -import com.xabber.android.data.connection.ConnectionItem; -import com.xabber.android.data.database.MessageDatabaseManager; -import com.xabber.android.data.database.messagerealm.Attachment; -import com.xabber.android.data.database.messagerealm.ForwardId; -import com.xabber.android.data.database.messagerealm.MessageItem; -import com.xabber.android.data.database.messagerealm.SyncInfo; -import com.xabber.android.data.entity.AccountJid; -import com.xabber.android.data.entity.BaseEntity; -import com.xabber.android.data.entity.UserJid; -import com.xabber.android.data.extension.file.FileManager; -import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager; -import com.xabber.android.data.extension.otr.OTRManager; -import com.xabber.android.data.log.LogManager; -import com.xabber.android.data.message.AbstractChat; -import com.xabber.android.data.message.ForwardManager; -import com.xabber.android.data.message.MessageManager; -import com.xabber.android.data.roster.OnRosterReceivedListener; -import com.xabber.android.data.roster.RosterContact; -import com.xabber.android.data.roster.RosterManager; - -import net.java.otr4j.io.SerializationUtils; -import net.java.otr4j.io.messages.PlainTextMessage; - -import org.greenrobot.eventbus.EventBus; -import org.jivesoftware.smack.SmackException; -import org.jivesoftware.smack.XMPPException; -import org.jivesoftware.smack.packet.Message; -import org.jivesoftware.smack.tcp.XMPPTCPConnection; -import org.jivesoftware.smack.util.PacketParserUtils; -import org.jivesoftware.smackx.delay.packet.DelayInformation; -import org.jivesoftware.smackx.forward.packet.Forwarded; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -import io.realm.Realm; -import io.realm.RealmList; -import io.realm.RealmResults; - -public class MamManager implements OnRosterReceivedListener { - static final String LOG_TAG = MamManager.class.getSimpleName(); - private static MamManager instance; - public static final int SYNC_INTERVAL_MINUTES = 5; - - public static int PAGE_SIZE = AbstractChat.PRELOADED_MESSAGES; - - private Map supportedByAccount; - - private boolean isRequested = false; - private final Object lock = new Object(); - - public static MamManager getInstance() { - if (instance == null) { - instance = new MamManager(); - } - - return instance; - } - - public MamManager() { - supportedByAccount = new ConcurrentHashMap<>(); - } - - public void onAuthorized(ConnectionItem connectionItem) { - updateIsSupported((AccountItem) connectionItem); - } - - - @Override - public void onRosterReceived(final AccountItem accountItem) { - LogManager.i(this, "onRosterReceived " + accountItem.getAccount()); - Application.getInstance().runOnUiThread(new Runnable() { - @Override - public void run() { - if (accountItem.getLoadHistorySettings() != LoadHistorySettings.all) { - return; - } - - Collection contacts = RosterManager.getInstance() - .getAccountRosterContacts(accountItem.getAccount()); - for (RosterContact contact : contacts) { - requestLastHistory(MessageManager.getInstance() - .getOrCreateChat(contact.getAccount(), contact.getUser())); - } - } - }); - } - - @Nullable - public Boolean isSupported(AccountJid accountJid) { - return supportedByAccount.get(accountJid); - } - - private boolean checkSupport(AccountItem accountItem) { - Boolean isSupported = supportedByAccount.get(accountItem.getAccount()); - - if (isSupported != null) { - return isSupported; - } - - return updateIsSupported(accountItem); - } - - private boolean updateIsSupported(AccountItem accountItem) { - org.jivesoftware.smackx.mam.MamManager mamManager = org.jivesoftware.smackx.mam.MamManager - .getInstanceFor(accountItem.getConnection()); - - boolean isSupported; - try { - isSupported = mamManager.isSupportedByServer(); - - if (isSupported) { - org.jivesoftware.smackx.mam.MamManager.MamPrefsResult archivingPreferences = mamManager.retrieveArchivingPreferences(); - LogManager.i(this, "archivingPreferences default behaviour " + archivingPreferences.mamPrefs.getDefault()); - org.jivesoftware.smackx.mam.MamManager.MamPrefsResult result - = mamManager.updateArchivingPreferences(null, null, accountItem.getMamDefaultBehaviour()); - LogManager.i(this, "updateArchivingPreferences result " + result.toString()); - } - - } catch (SmackException.NoResponseException | XMPPException.XMPPErrorException - | InterruptedException | SmackException.NotConnectedException | SmackException.NotLoggedInException - | ClassCastException e) { - LogManager.exception(this, e); - return false; - } - - LogManager.i(this, "MAM support for account " + accountItem.getAccount() + " " + isSupported); - supportedByAccount.put(accountItem.getAccount(), isSupported); - - AccountManager.getInstance().onAccountChanged(accountItem.getAccount()); - return isSupported; - } - - public void requestUpdatePreferences(final AccountJid accountJid) { - Application.getInstance().runInBackgroundUserRequest(new Runnable() { - @Override - public void run() { - AccountItem accountItem = AccountManager.getInstance().getAccount(accountJid); - if (accountItem == null) { - return; - } - org.jivesoftware.smackx.mam.MamManager mamManager = org.jivesoftware.smackx.mam.MamManager - .getInstanceFor(accountItem.getConnection()); - - try { - org.jivesoftware.smackx.mam.MamManager.MamPrefsResult result - = mamManager.updateArchivingPreferences(null, null, accountItem.getMamDefaultBehaviour()); - LogManager.i(LOG_TAG, "MAM default behavior updated to " + result.mamPrefs.getDefault()); - } catch (SmackException.NoResponseException | XMPPException.XMPPErrorException - | InterruptedException | SmackException.NotConnectedException - | SmackException.NotLoggedInException e) { - LogManager.exception(LOG_TAG, e); - } - - } - }); - } - - public void requestLastHistoryByUser(final AbstractChat chat) { - Application.getInstance().runInBackgroundUserRequest(new Runnable() { - @Override - public void run() { - getLastHistory(chat, false); - } - }); - } - - private void requestLastHistory(final AbstractChat chat) { - Application.getInstance().runInBackground(new Runnable() { - @Override - public void run() { - getLastHistory(chat, true); - } - }); - } - - private boolean isTimeToRefreshHistory(AbstractChat chat) { - return chat.getLastSyncedTime() != null - && TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - chat.getLastSyncedTime().getTime()) - < SYNC_INTERVAL_MINUTES; - } - - @SuppressWarnings("WeakerAccess") - void getLastHistory(AbstractChat chat, boolean ignoreTime) { - if (chat == null) { - return; - } - - if (!ignoreTime) { - if (isTimeToRefreshHistory(chat)) { - return; - } - } - - final AccountItem accountItem = AccountManager.getInstance().getAccount(chat.getAccount()); - if (accountItem == null) { - return; - } - - XMPPTCPConnection connection = accountItem.getConnection(); - if (!connection.isAuthenticated()) { - return; - } - - if (!checkSupport(accountItem)) { - return; - } - - EventBus.getDefault().post(new LastHistoryLoadStartedEvent(chat)); - - org.jivesoftware.smackx.mam.MamManager mamManager - = org.jivesoftware.smackx.mam.MamManager.getInstanceFor(connection); - - String lastMessageMamId; - int receivedMessagesCount; - do { - Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm(); - lastMessageMamId = getSyncInfo(realm, chat.getAccount(), chat.getUser()).getLastMessageMamId(); - realm.close(); - - receivedMessagesCount = requestLastHistoryPage(mamManager, chat, lastMessageMamId); - - // if it was NOT the first time, and we got exactly one page, - // it means that there should be more unloaded recent history - } while (lastMessageMamId != null && receivedMessagesCount == PAGE_SIZE); - - // if it was first time receiving history, and we got less than a page - // it mean that all previous history loaded - if (lastMessageMamId == null - && receivedMessagesCount >= 0 && receivedMessagesCount < PAGE_SIZE) { - setRemoteHistoryCompletelyLoaded(chat); - } - - EventBus.getDefault().post(new LastHistoryLoadFinishedEvent(chat)); - } - - public void setRemoteHistoryCompletelyLoaded(AbstractChat chat) { - LogManager.i(this, "setRemoteHistoryCompletelyLoaded " + chat.getUser()); - - Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm(); - SyncInfo syncInfo = getSyncInfo(realm, chat.getAccount(), chat.getUser()); - realm.beginTransaction(); - syncInfo.setRemoteHistoryCompletelyLoaded(true); - realm.commitTransaction(); - realm.close(); - } - - private int requestLastHistoryPage(org.jivesoftware.smackx.mam.MamManager mamManager, - AbstractChat chat, String lastMessageMamId) { - final org.jivesoftware.smackx.mam.MamManager.MamQueryResult mamQueryResult; - try { - if (lastMessageMamId == null) { - mamQueryResult = mamManager.pageBefore(chat.getUser().getJid(), "", PAGE_SIZE); - } else { - mamQueryResult = mamManager.pageAfter(chat.getUser().getJid(), lastMessageMamId, PAGE_SIZE); - } - } catch (SmackException.NotLoggedInException | InterruptedException - | SmackException.NotConnectedException | SmackException.NoResponseException | XMPPException.XMPPErrorException e) { - LogManager.exception(this, e); - return -1; - } - - int receivedMessagesCount = mamQueryResult.forwardedMessages.size(); - - LogManager.i(this, "receivedMessagesCount " + receivedMessagesCount); - - chat.setLastSyncedTime(new Date(System.currentTimeMillis())); - - Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm(); - updateLastHistorySyncInfo(realm, chat, mamQueryResult); - syncMessages(realm, chat, getMessageItems(mamQueryResult, chat)); - realm.close(); - - return receivedMessagesCount; - } - - private void syncMessages(Realm realm, AbstractChat chat, final Collection messagesFromServer) { - - if (messagesFromServer == null || messagesFromServer.isEmpty()) { - return; - } - - LogManager.i(this, "syncMessages: " + messagesFromServer.size()); - - RealmResults localMessages = realm.where(MessageItem.class) - .equalTo(MessageItem.Fields.ACCOUNT, chat.getAccount().toString()) - .equalTo(MessageItem.Fields.USER, chat.getUser().toString()) - .findAll(); - - Iterator iterator = messagesFromServer.iterator(); - while (iterator.hasNext()) { - MessageItem remoteMessage = iterator.next(); - - // set text from comment to text in message for prevent doubling messages from MAM - Message originalMessage = null; - try { - originalMessage = (Message) PacketParserUtils.parseStanza(remoteMessage.getOriginalStanza()); - String comment = ForwardManager.parseForwardComment(originalMessage); - if (comment != null) remoteMessage.setText(comment); - } catch (Exception e) { - e.printStackTrace(); - } - - // assume that Stanza ID could be not unique - if (localMessages.where() - .equalTo(MessageItem.Fields.STANZA_ID, remoteMessage.getStanzaId()) - .equalTo(MessageItem.Fields.TEXT, remoteMessage.getText()) - .count() > 0) { - LogManager.i(this, "Sync. Removing message with same Stanza ID and text. Remote message:" - + " Text: " + remoteMessage.getText() - + " Timestamp: " + remoteMessage.getTimestamp() - + " Delay Timestamp: " + remoteMessage.getDelayTimestamp() - + " StanzaId: " + remoteMessage.getStanzaId()); - iterator.remove(); - continue; - } - - Long remoteMessageDelayTimestamp = remoteMessage.getDelayTimestamp(); - Long remoteMessageTimestamp = remoteMessage.getTimestamp(); - - RealmResults sameTextMessages = localMessages.where() - .equalTo(MessageItem.Fields.TEXT, remoteMessage.getText()).findAll(); - - if (isTimeStampSimilar(sameTextMessages, remoteMessageTimestamp)) { - LogManager.i(this, "Sync. Found messages with same text and similar remote timestamp. Removing. Remote message:" - + " Text: " + remoteMessage.getText() - + " Timestamp: " + remoteMessage.getTimestamp() - + " Delay Timestamp: " + remoteMessage.getDelayTimestamp() - + " StanzaId: " + remoteMessage.getStanzaId()); - iterator.remove(); - continue; - } - - if (remoteMessageDelayTimestamp != null - && isTimeStampSimilar(sameTextMessages, remoteMessageDelayTimestamp)) { - LogManager.i(this, "Sync. Found messages with same text and similar remote delay timestamp. Removing. Remote message:" - + " Text: " + remoteMessage.getText() - + " Timestamp: " + remoteMessage.getTimestamp() - + " Delay Timestamp: " + remoteMessage.getDelayTimestamp() - + " StanzaId: " + remoteMessage.getStanzaId()); - iterator.remove(); - continue; - } - - // forwarded - if (originalMessage != null) { - RealmList forwardIds = chat.parseForwardedMessage(false, originalMessage, remoteMessage.getUniqueId()); - if (forwardIds != null && !forwardIds.isEmpty()) - remoteMessage.setForwardedIds(forwardIds); - } - } - - realm.beginTransaction(); - realm.copyToRealm(messagesFromServer); - realm.commitTransaction(); - } - - private static boolean isTimeStampSimilar(RealmResults sameTextMessages, long remoteMessageTimestamp) { - long start = remoteMessageTimestamp - (1000 * 5); - long end = remoteMessageTimestamp + (1000 * 5); - - if (sameTextMessages.where() - .between(MessageItem.Fields.TIMESTAMP, start, end) - .count() > 0) { - LogManager.i(MamManager.class.getSimpleName(), "Sync. Found messages with similar local timestamp"); - return true; - } - - if (sameTextMessages.where() - .between(MessageItem.Fields.DELAY_TIMESTAMP, start, end) - .count() > 0) { - LogManager.i(MamManager.class.getSimpleName(), "Sync. Found messages with similar local delay timestamp."); - return true; - } - return false; - } - - @NonNull - private SyncInfo getSyncInfo(Realm realm, AccountJid account, UserJid user) { - SyncInfo syncInfo = realm.where(SyncInfo.class) - .equalTo(SyncInfo.FIELD_ACCOUNT, account.toString()) - .equalTo(SyncInfo.FIELD_USER, user.toString()).findFirst(); - - if (syncInfo == null) { - realm.beginTransaction(); - syncInfo = realm.createObject(SyncInfo.class); - syncInfo.setAccount(account); - syncInfo.setUser(user); - realm.commitTransaction(); - } - return syncInfo; - } - - private void updateLastHistorySyncInfo(Realm realm, BaseEntity chat, org.jivesoftware.smackx.mam.MamManager.MamQueryResult mamQueryResult) { - SyncInfo syncInfo = getSyncInfo(realm, chat.getAccount(), chat.getUser()); - - realm.beginTransaction(); - - if (mamQueryResult.mamFin.getRSMSet() != null) { - - if (syncInfo.getFirstMamMessageMamId() == null) { - syncInfo.setFirstMamMessageMamId(mamQueryResult.mamFin.getRSMSet().getFirst()); - if (!mamQueryResult.forwardedMessages.isEmpty()) { - syncInfo.setFirstMamMessageStanzaId(mamQueryResult.forwardedMessages.get(0).getForwardedStanza().getStanzaId()); - } - } - if (mamQueryResult.mamFin.getRSMSet().getLast() != null) { - syncInfo.setLastMessageMamId(mamQueryResult.mamFin.getRSMSet().getLast()); - } - - } - - realm.commitTransaction(); - } - - public void requestPreviousHistory(final AbstractChat chat) { - if (chat == null || chat.isRemotePreviousHistoryCompletelyLoaded()) { - return; - } - - final AccountItem accountItem = AccountManager.getInstance().getAccount(chat.getAccount()); - if (accountItem == null || !accountItem.getFactualStatusMode().isOnline()) { - return; - } - - Application.getInstance().runInBackgroundUserRequest(new Runnable() { - @Override - public void run() { - if (!checkSupport(accountItem)) { - return; - } - - synchronized (lock) { - if (isRequested) return; - else isRequested = true; - } - - String firstMamMessageMamId; - boolean remoteHistoryCompletelyLoaded; - { - Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm(); - SyncInfo syncInfo = getSyncInfo(realm, chat.getAccount(), chat.getUser()); - firstMamMessageMamId = syncInfo.getFirstMamMessageMamId(); - remoteHistoryCompletelyLoaded = syncInfo.isRemoteHistoryCompletelyLoaded(); - realm.close(); - } - - if (remoteHistoryCompletelyLoaded) { - chat.setRemotePreviousHistoryCompletelyLoaded(true); - } - - if (firstMamMessageMamId == null || remoteHistoryCompletelyLoaded) { - disableLock(); - return; - } - - org.jivesoftware.smackx.mam.MamManager mamManager = org.jivesoftware.smackx.mam.MamManager.getInstanceFor(accountItem.getConnection()); - - final org.jivesoftware.smackx.mam.MamManager.MamQueryResult mamQueryResult; - try { - EventBus.getDefault().post(new PreviousHistoryLoadStartedEvent(chat)); - LogManager.i("MAM", "Loading previous history"); - mamQueryResult = mamManager.pageBefore(chat.getUser().getJid(), firstMamMessageMamId, PAGE_SIZE); - } catch (SmackException.NotLoggedInException | SmackException.NoResponseException | XMPPException.XMPPErrorException | InterruptedException | SmackException.NotConnectedException e) { - LogManager.exception(this, e); - EventBus.getDefault().post(new PreviousHistoryLoadFinishedEvent(chat)); - disableLock(); - return; - } - - LogManager.i("MAM", "queryArchive finished. fin count expected: " + mamQueryResult.mamFin.getRSMSet().getCount() + " real: " + mamQueryResult.forwardedMessages.size()); - - Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm(); - updatePreviousHistorySyncInfo(realm, chat, mamQueryResult); - List messageItems = getMessageItems(mamQueryResult, chat); - syncMessages(realm, chat, messageItems); - realm.close(); - - EventBus.getDefault().post(new PreviousHistoryLoadFinishedEvent(chat)); - disableLock(); - } - - }); - - } - - private void disableLock() { - synchronized (lock) { - isRequested = false; - } - } - - private void updatePreviousHistorySyncInfo(Realm realm, BaseEntity chat, - org.jivesoftware.smackx.mam.MamManager.MamQueryResult mamQueryResult) { - SyncInfo syncInfo = getSyncInfo(realm, chat.getAccount(), chat.getUser()); - - realm.beginTransaction(); - if (mamQueryResult.forwardedMessages.size() < PAGE_SIZE) { - syncInfo.setRemoteHistoryCompletelyLoaded(true); - } - - syncInfo.setFirstMamMessageMamId(mamQueryResult.mamFin.getRSMSet().getFirst()); - if (!mamQueryResult.forwardedMessages.isEmpty()) { - syncInfo.setFirstMamMessageStanzaId(mamQueryResult.forwardedMessages.get(0).getForwardedStanza().getStanzaId()); - } - realm.commitTransaction(); - } - - private List getMessageItems(org.jivesoftware.smackx.mam.MamManager.MamQueryResult mamQueryResult, AbstractChat chat) { - List messageItems = new ArrayList<>(); - - for (Forwarded forwarded : mamQueryResult.forwardedMessages) { - if (!(forwarded.getForwardedStanza() instanceof Message)) { - continue; - } - - Message message = (Message) forwarded.getForwardedStanza(); - - DelayInformation delayInformation = forwarded.getDelayInformation(); - - DelayInformation messageDelay = DelayInformation.from(message); - - String body = message.getBody(); - net.java.otr4j.io.messages.AbstractMessage otrMessage; - try { - otrMessage = SerializationUtils.toMessage(body); - } catch (IOException e) { - continue; - } - boolean encrypted = false; - if (otrMessage != null) { - if (otrMessage.messageType != net.java.otr4j.io.messages.AbstractMessage.MESSAGE_PLAINTEXT) { - encrypted = true; - try { - // this transforming just decrypt message if have keys. No action as injectMessage or something else - body = OTRManager.getInstance().transformReceivingIfSessionExist(chat.getAccount(), chat.getUser(), body); - if (OTRManager.getInstance().isEncrypted(body)) { - continue; - } - } catch (Exception e) { - continue; - } - } - else body = ((PlainTextMessage) otrMessage).cleanText; - } - - boolean incoming = message.getFrom().asBareJid().equals(chat.getUser().getJid().asBareJid()); - - String uid = UUID.randomUUID().toString(); - MessageItem messageItem = new MessageItem(uid); - - messageItem.setAccount(chat.getAccount()); - messageItem.setUser(chat.getUser()); - messageItem.setResource(chat.getUser().getJid().getResourceOrNull()); - messageItem.setText(body); - messageItem.setTimestamp(delayInformation.getStamp().getTime()); - if (messageDelay != null) { - messageItem.setDelayTimestamp(messageDelay.getStamp().getTime()); - } - messageItem.setIncoming(incoming); - messageItem.setStanzaId(message.getStanzaId()); - messageItem.setReceivedFromMessageArchive(true); - messageItem.setRead(true); - messageItem.setSent(true); - messageItem.setEncrypted(encrypted); - - // attachments - FileManager.processFileMessage(messageItem); - - RealmList attachments = HttpFileUploadManager.parseFileMessage(message); - if (attachments.size() > 0) - messageItem.setAttachments(attachments); - - // forwarded - messageItem.setOriginalStanza(message.toXML().toString()); - messageItem.setOriginalFrom(message.getFrom().toString()); - - messageItems.add(messageItem); - } - return messageItems; - } - - /** - * Only for debugging - * Call only from background thread - * @param chat - */ - public void requestFullChatHistory(final AbstractChat chat) { - if (chat == null || chat.isRemotePreviousHistoryCompletelyLoaded()) { - return; - } - - final AccountItem accountItem = AccountManager.getInstance().getAccount(chat.getAccount()); - if (accountItem == null || !accountItem.getFactualStatusMode().isOnline()) { - return; - } - - if (!checkSupport(accountItem)) { - return; - } - - String firstMamMessageMamId; - boolean remoteHistoryCompletelyLoaded; - { - Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm(); - SyncInfo syncInfo = getSyncInfo(realm, chat.getAccount(), chat.getUser()); - firstMamMessageMamId = syncInfo.getFirstMamMessageMamId(); - remoteHistoryCompletelyLoaded = syncInfo.isRemoteHistoryCompletelyLoaded(); - realm.close(); - } - - if (remoteHistoryCompletelyLoaded) { - chat.setRemotePreviousHistoryCompletelyLoaded(true); - } - - if (firstMamMessageMamId == null || remoteHistoryCompletelyLoaded) { - return; - } - - org.jivesoftware.smackx.mam.MamManager mamManager = - org.jivesoftware.smackx.mam.MamManager.getInstanceFor(accountItem.getConnection()); - - final org.jivesoftware.smackx.mam.MamManager.MamQueryResult mamQueryResult; - try { - LogManager.i("MAM", "Loading previous history"); - mamQueryResult = mamManager.queryArchive(chat.getUser().getJid()); - } catch (SmackException.NotLoggedInException | SmackException.NoResponseException - | XMPPException.XMPPErrorException | InterruptedException - | SmackException.NotConnectedException e) { - LogManager.exception(this, e); - return; - } - LogManager.i("MAM", "queryArchive finished. fin count expected: " - + mamQueryResult.mamFin.getRSMSet().getCount() + " real: " - + mamQueryResult.forwardedMessages.size()); - - Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm(); - List messageItems = getMessageItems(mamQueryResult, chat); - syncMessages(realm, chat, messageItems); - updatePreviousHistorySyncInfo(realm, chat, mamQueryResult); - realm.close(); - } -} diff --git a/xabber/src/main/java/com/xabber/android/data/extension/mam/NextMamManager.java b/xabber/src/main/java/com/xabber/android/data/extension/mam/NextMamManager.java new file mode 100644 index 0000000000..f122b08ea1 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/extension/mam/NextMamManager.java @@ -0,0 +1,1000 @@ +package com.xabber.android.data.extension.mam; + +import android.util.Pair; + +import com.xabber.android.data.Application; +import com.xabber.android.data.account.AccountItem; +import com.xabber.android.data.account.AccountManager; +import com.xabber.android.data.connection.ConnectionItem; +import com.xabber.android.data.connection.listeners.OnPacketListener; +import com.xabber.android.data.database.MessageDatabaseManager; +import com.xabber.android.data.database.messagerealm.Attachment; +import com.xabber.android.data.database.messagerealm.ForwardId; +import com.xabber.android.data.database.messagerealm.MessageItem; +import com.xabber.android.data.database.messagerealm.SyncInfo; +import com.xabber.android.data.entity.AccountJid; +import com.xabber.android.data.entity.UserJid; +import com.xabber.android.data.extension.file.FileManager; +import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager; +import com.xabber.android.data.extension.otr.OTRManager; +import com.xabber.android.data.extension.references.ReferencesManager; +import com.xabber.android.data.log.LogManager; +import com.xabber.android.data.message.AbstractChat; +import com.xabber.android.data.message.ForwardManager; +import com.xabber.android.data.message.MessageManager; +import com.xabber.android.data.message.NewMessageEvent; +import com.xabber.android.data.notification.NotificationManager; +import com.xabber.android.data.push.SyncManager; +import com.xabber.android.data.roster.OnRosterReceivedListener; +import com.xabber.android.data.roster.RosterContact; +import com.xabber.android.data.roster.RosterManager; + +import net.java.otr4j.io.SerializationUtils; +import net.java.otr4j.io.messages.PlainTextMessage; + +import org.greenrobot.eventbus.EventBus; +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smack.tcp.XMPPTCPConnection; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.delay.packet.DelayInformation; +import org.jivesoftware.smackx.forward.packet.Forwarded; +import org.jivesoftware.smackx.mam.MamManager; +import org.jivesoftware.smackx.mam.element.MamElements; +import org.jivesoftware.smackx.mam.element.MamFinIQ; +import org.jivesoftware.smackx.mam.element.MamPrefsIQ; +import org.jivesoftware.smackx.mam.element.MamQueryIQ; +import org.jivesoftware.smackx.rsm.packet.RSMSet; +import org.jivesoftware.smackx.xdata.FormField; +import org.jivesoftware.smackx.xdata.packet.DataForm; +import org.jxmpp.jid.Jid; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import io.realm.Realm; +import io.realm.RealmList; +import io.realm.RealmResults; +import io.realm.Sort; + +public class NextMamManager implements OnRosterReceivedListener, OnPacketListener { + + private static final String LOG_TAG = NextMamManager.class.getSimpleName(); + + private static NextMamManager instance; + + private Map supportedByAccount = new ConcurrentHashMap<>(); + private boolean isRequested = false; + private final Object lock = new Object(); + private Map waitingRequests = new HashMap<>(); + + public static NextMamManager getInstance() { + if (instance == null) + instance = new NextMamManager(); + return instance; + } + + @Override + public void onRosterReceived(AccountItem accountItem) { + onAccountConnected(accountItem); + } + + public void onAccountConnected(AccountItem accountItem) { + updateIsSupported(accountItem); + updatePreferencesFromServer(accountItem); + Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm(); + accountItem.setStartHistoryTimestamp(getLastMessageTimestamp(accountItem, realm)); + if (accountItem.getStartHistoryTimestamp() == 0) { + initializeStartTimestamp(realm, accountItem); + loadLastMessagesAsync(accountItem); + } else { + if (isNeedMigration(accountItem, realm)) { + runMigrationToNewArchive(accountItem, realm); + } + String lastArchivedId = getLastMessageArchivedId(accountItem, realm); + if (lastArchivedId != null) { + boolean historyCompleted = loadAllNewMessages(realm, accountItem, lastArchivedId); + if (!historyCompleted) loadLastMessagesAsync(accountItem); + } else loadLastMessagesAsync(accountItem); + + loadLastMessagesInMissedChatsAsync(realm, accountItem); + } + realm.close(); + } + + public void onChatOpen(final AbstractChat chat) { + final AccountItem accountItem = AccountManager.getInstance().getAccount(chat.getAccount()); + if (accountItem == null || accountItem.getLoadHistorySettings() == LoadHistorySettings.none + || !isSupported(accountItem.getAccount())) return; + + Application.getInstance().runInBackground(new Runnable() { + @Override + public void run() { + Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm(); + + // if history is empty - load last message + MessageItem firstMessage = getFirstMessage(chat, realm); + if (firstMessage == null) loadLastMessage(realm, accountItem, chat); + + synchronized (lock) { + if (isRequested) return; + else isRequested = true; + } + + // load prev page if history is not enough + if (historyIsNotEnough(realm, chat) && !chat.historyIsFull()) { + EventBus.getDefault().post(new LastHistoryLoadStartedEvent(chat)); + loadNextHistory(realm, accountItem, chat); + EventBus.getDefault().post(new LastHistoryLoadFinishedEvent(chat)); + } + + // load missed messages if need + List messages = findMissedMessages(realm, chat); + if (messages != null && !messages.isEmpty() && accountItem != null) { + for (MessageItem message : messages) { + loadMissedMessages(realm, accountItem, chat, message); + } + } + + synchronized (lock) { + isRequested = false; + } + realm.close(); + } + }); + } + + public void onScrollInChat(final AbstractChat chat) { + final AccountItem accountItem = AccountManager.getInstance().getAccount(chat.getAccount()); + if (accountItem == null || accountItem.getLoadHistorySettings() == LoadHistorySettings.none + || !isSupported(accountItem.getAccount())) return; + + if (chat.historyIsFull()) return; + Application.getInstance().runInBackground(new Runnable() { + @Override + public void run() { + synchronized (lock) { + if (isRequested) return; + else isRequested = true; + } + EventBus.getDefault().post(new LastHistoryLoadStartedEvent(chat)); + Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm(); + loadNextHistory(realm, accountItem, chat); + realm.close(); + EventBus.getDefault().post(new LastHistoryLoadFinishedEvent(chat)); + synchronized (lock) { + isRequested = false; + } + } + }); + } + + public void loadFullChatHistory(AbstractChat chat) { + final AccountItem accountItem = AccountManager.getInstance().getAccount(chat.getAccount()); + if (accountItem == null || !isSupported(accountItem.getAccount()) || chat.historyIsFull()) return; + + Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm(); + + // if history is empty - load last message + MessageItem firstMessage = getFirstMessage(chat, realm); + if (firstMessage == null) loadLastMessage(realm, accountItem, chat); + + boolean complete = false; + while (!complete) { + complete = loadNextHistory(realm, accountItem, chat); + } + + realm.close(); + } + + public void onRequestUpdatePreferences(AccountJid accountJid) { + final AccountItem accountItem = AccountManager.getInstance().getAccount(accountJid); + if (accountItem == null || !isSupported(accountJid)) return; + + Application.getInstance().runInBackgroundUserRequest(new Runnable() { + @Override + public void run() { + requestUpdatePreferences(accountItem); + } + }); + } + + @Override + public void onStanza(ConnectionItem connection, Stanza packet) { + if (packet instanceof Message) { + for (ExtensionElement packetExtension : packet.getExtensions()) { + if (packetExtension instanceof MamElements.MamResultExtension) { + MamElements.MamResultExtension resultExtension = + (MamElements.MamResultExtension) packetExtension; + String resultID = resultExtension.getQueryId(); + if (waitingRequests.containsKey(resultID)) { + Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread(); + parseAndSaveMessageFromMamResult(realm, connection.getAccount(), resultExtension.getForwarded()); + UserJid userJid = waitingRequests.get(resultID); + AbstractChat chat = MessageManager.getInstance().getChat(connection.getAccount(), userJid); + if (chat != null && !chat.isHistoryRequestedAtStart()) + chat.setHistoryRequestedAtStart(true); + waitingRequests.remove(resultID); + } + } + } + } + if (packet instanceof MamFinIQ) { + MamFinIQ finIQ = (MamFinIQ) packet; + if (finIQ.isComplete() && waitingRequests.containsKey(finIQ.getQueryId())) { + UserJid userJid = waitingRequests.get(finIQ.getQueryId()); + AbstractChat chat = MessageManager.getInstance().getChat(connection.getAccount(), userJid); + if (chat != null) { + if (!chat.isHistoryRequestedAtStart()) + chat.setHistoryRequestedAtStart(true); + } + } + } + } + + public boolean isSupported(AccountJid accountJid) { + Boolean isSupported = supportedByAccount.get(accountJid); + if (isSupported != null) return isSupported; + else return false; + } + + /** MAIN */ + + /** For load messages that was missed because of errors or crash */ + private void loadLastMessagesInMissedChatsAsync(Realm realm, AccountItem accountItem) { + if (accountItem.getLoadHistorySettings() != LoadHistorySettings.all + || !isSupported(accountItem.getAccount())) return; + + Collection contacts = RosterManager.getInstance() + .getAccountRosterContacts(accountItem.getAccount()); + + for (RosterContact contact : contacts) { + AbstractChat chat = MessageManager.getInstance() + .getOrCreateChat(contact.getAccount(), contact.getUser()); + if (getFirstMessage(chat, realm) == null && !chat.isHistoryRequestedAtStart()) { + LogManager.d(LOG_TAG, "load missed messages in: " + contact.getUser()); + requestLastMessageAsync(accountItem, chat); + } + } + } + + private void loadLastMessagesAsync(AccountItem accountItem) { + if (accountItem.getLoadHistorySettings() != LoadHistorySettings.all + || !isSupported(accountItem.getAccount())) return; + + LogManager.d(LOG_TAG, "load last messages in each chat"); + Collection contacts = RosterManager.getInstance() + .getAccountRosterContacts(accountItem.getAccount()); + + for (RosterContact contact : contacts) { + AbstractChat chat = MessageManager.getInstance() + .getOrCreateChat(contact.getAccount(), contact.getUser()); + requestLastMessageAsync(accountItem, chat); + } + } + + private void loadLastMessage(Realm realm, AccountItem accountItem, AbstractChat chat) { + LogManager.d(LOG_TAG, "load last messages in chat: " + chat.getUser()); + MamManager.MamQueryResult queryResult = requestLastMessage(accountItem, chat); + if (queryResult != null) { + List messages = new ArrayList<>(queryResult.forwardedMessages); + saveOrUpdateMessages(realm, parseMessage(accountItem, chat.getAccount(), chat.getUser(), messages, null)); + } + updateLastMessageId(chat, realm); + } + + private boolean loadAllNewMessages(Realm realm, AccountItem accountItem, String lastArchivedId) { + if (accountItem.getLoadHistorySettings() != LoadHistorySettings.all + || !isSupported(accountItem.getAccount())) return true; + + LogManager.d(LOG_TAG, "load new messages"); + List messages = new ArrayList<>(); + boolean complete = false; + String id = lastArchivedId; + int pageLoaded = 0; + // Request all new messages after last archived id + while (!complete && id != null && pageLoaded < 2) { + MamManager.MamQueryResult queryResult = requestMessagesFromId(accountItem, null, id); + if (queryResult != null) { + messages.addAll(queryResult.forwardedMessages); + complete = queryResult.mamFin.isComplete(); + id = getNextId(queryResult); + pageLoaded++; + } else complete = true; + } + + if (!messages.isEmpty()) { + HashMap> messagesByChat = new HashMap<>(); + List parsedMessages = new ArrayList<>(); + List chatsNeedUpdateLastMessageId = new ArrayList<>(); + + // Sort messages by chat to separate lists + for (Forwarded forwarded : messages) { + Stanza stanza = forwarded.getForwardedStanza(); + Jid user = stanza.getFrom().asBareJid(); + if (user.equals(accountItem.getAccount().getFullJid().asBareJid())) + user = stanza.getTo().asBareJid(); + + if (!messagesByChat.containsKey(user.toString())) { + messagesByChat.put(user.toString(), new ArrayList()); + } + ArrayList list = messagesByChat.get(user.toString()); + if (list != null) list.add(forwarded); + } + + // parse message lists + for (Map.Entry> entry : messagesByChat.entrySet()) { + ArrayList list = entry.getValue(); + if (list != null) { + try { + AbstractChat chat = MessageManager.getInstance() + .getOrCreateChat(accountItem.getAccount(), UserJid.from(entry.getKey())); + + // sort messages in list by timestamp + Collections.sort(list, new Comparator() { + @Override + public int compare(Forwarded o1, Forwarded o2) { + DelayInformation delayInformation1 = o1.getDelayInformation(); + long time1 = delayInformation1.getStamp().getTime(); + + DelayInformation delayInformation2 = o2.getDelayInformation(); + long time2 = delayInformation2.getStamp().getTime(); + + return Long.valueOf(time1).compareTo(time2); + } + }); + + // parse messages and set previous id + parsedMessages.addAll( + parseMessage(accountItem, accountItem.getAccount(), + chat.getUser(), list, chat.getLastMessageId())); + chatsNeedUpdateLastMessageId.add(chat); + + } catch (UserJid.UserJidCreateException e) { + LogManager.d(LOG_TAG, e.toString()); + continue; + } + } + } + + // save messages to Realm + saveOrUpdateMessages(realm, parsedMessages); + for (AbstractChat chat : chatsNeedUpdateLastMessageId) { + updateLastMessageId(chat, realm); + } + } + return complete; + } + + private boolean loadNextHistory(Realm realm, AccountItem accountItem, AbstractChat chat) { + LogManager.d(LOG_TAG, "load next history in chat: " + chat.getUser()); + MessageItem firstMessage = getFirstMessage(chat, realm); + if (firstMessage != null) { + if (firstMessage.getArchivedId().equals(firstMessage.getPreviousId())) { + chat.setHistoryIsFull(); + return true; + } + + MamManager.MamQueryResult queryResult = requestMessagesBeforeId(accountItem, chat, firstMessage.getArchivedId()); + if (queryResult != null) { + List messages = new ArrayList<>(queryResult.forwardedMessages); + if (!messages.isEmpty()) { + List savedMessages = saveOrUpdateMessages(realm, + parseMessage(accountItem, chat.getAccount(), chat.getUser(), messages, null)); + + if (savedMessages != null && !savedMessages.isEmpty()) { + realm.beginTransaction(); + firstMessage.setPreviousId(savedMessages.get(savedMessages.size() - 1).getArchivedId()); + realm.commitTransaction(); + return false; + } + } else if (queryResult.mamFin.isComplete()) { + realm.beginTransaction(); + firstMessage.setPreviousId(firstMessage.getArchivedId()); + realm.commitTransaction(); + } + } + } + return true; + } + + private void loadMissedMessages(Realm realm, AccountItem accountItem, AbstractChat chat, MessageItem m1) { + LogManager.d(LOG_TAG, "load missed messages in chat: " + chat.getUser()); + MessageItem m2 = getMessageForCloseMissedMessages(realm, m1); + if (m2 != null && !m2.getUniqueId().equals(m1.getUniqueId())) { + Date startDate = new Date(m2.getTimestamp()); + Date endDate = new Date(m1.getTimestamp()); + + List messages = new ArrayList<>(); + boolean complete = false; + + while (!complete && startDate != null) { + MamManager.MamQueryResult queryResult = requestMissedMessages(accountItem, chat, startDate, endDate); + if (queryResult != null) { + messages.addAll(queryResult.forwardedMessages); + complete = queryResult.mamFin.isComplete(); + startDate = getNextDate(queryResult); + } else complete = true; + } + + if (!messages.isEmpty()) { + List savedMessages = saveOrUpdateMessages(realm, + parseMessage(accountItem, chat.getAccount(), chat.getUser(), messages, m2.getArchivedId())); + + if (savedMessages != null && !savedMessages.isEmpty()) { + realm.beginTransaction(); + m1.setPreviousId(savedMessages.get(savedMessages.size() - 1).getArchivedId()); + realm.commitTransaction(); + } + } else { + realm.beginTransaction(); + m1.setPreviousId(m2.getArchivedId()); + realm.commitTransaction(); + } + } + } + + /** Request most recent message from all history and save it timestamp to startHistoryTimestamp + * If message is null save current time to startHistoryTimestamp */ + private void initializeStartTimestamp(Realm realm, @Nonnull AccountItem accountItem) { + long startHistoryTimestamp = System.currentTimeMillis(); + + MamManager.MamQueryResult queryResult = requestLastMessage(accountItem, null); + if (queryResult != null && !queryResult.forwardedMessages.isEmpty()) { + Forwarded forwarded = queryResult.forwardedMessages.get(0); + startHistoryTimestamp = forwarded.getDelayInformation().getStamp().getTime(); + parseAndSaveMessageFromMamResult(realm, accountItem.getAccount(), forwarded); + } + accountItem.setStartHistoryTimestamp(startHistoryTimestamp); + } + + private void updateIsSupported(AccountItem accountItem) { + MamManager mamManager = MamManager.getInstanceFor(accountItem.getConnection()); + boolean isSupported; + try { + isSupported = mamManager.isSupportedByServer(); + } catch (SmackException.NoResponseException | XMPPException.XMPPErrorException + | InterruptedException | SmackException.NotConnectedException | ClassCastException e) { + LogManager.exception(this, e); + isSupported = false; + } + supportedByAccount.put(accountItem.getAccount(), isSupported); + AccountManager.getInstance().onAccountChanged(accountItem.getAccount()); + } + + private void updatePreferencesFromServer(@Nonnull AccountItem accountItem) { + MamManager.MamPrefsResult prefsResult = requestPreferencesFromServer(accountItem); + if (prefsResult != null) { + MamPrefsIQ.DefaultBehavior behavior = prefsResult.mamPrefs.getDefault(); + AccountManager.getInstance().setMamDefaultBehaviour(accountItem.getAccount(), behavior); + } + } + + /** REQUESTS */ + + /** T extends MamManager.MamQueryResult or T extends MamManager.MamPrefsResult */ + abstract class MamRequest { + abstract T execute(MamManager manager) throws Exception; + } + + /** T extends MamManager.MamQueryResult or T extends MamManager.MamPrefsResult */ + private T requestToMessageArchive(AccountItem accountItem, MamRequest request) { + T result = null; + XMPPTCPConnection connection = accountItem.getConnection(); + + if (connection.isAuthenticated()) { + MamManager mamManager = MamManager.getInstanceFor(connection); + try { + result = request.execute(mamManager); + } catch (Exception e) { + LogManager.exception(this, e); + } + } + return result; + } + + /** Request recent message from chat history if chat not null + * Else request most recent message from all history*/ + private @Nullable MamManager.MamQueryResult requestLastMessage( + @Nonnull AccountItem accountItem, @Nullable final AbstractChat chat) { + + return requestToMessageArchive(accountItem, new MamRequest() { + @Override + MamManager.MamQueryResult execute(MamManager manager) throws Exception { + if (chat != null) return manager.mostRecentPage(chat.getUser().getJid(), 1); + else return manager.mostRecentPage(null, 1); + } + }); + } + + /** Send async request for recent message from chat history */ + private void requestLastMessageAsync(@Nonnull final AccountItem accountItem, @Nonnull final AbstractChat chat) { + requestToMessageArchive(accountItem, new MamRequest() { + @Override + MamManager.MamQueryResult execute(MamManager manager) throws Exception { + // add request id to waiting list + String queryID = UUID.randomUUID().toString(); + waitingRequests.put(queryID, chat.getUser()); + + // send request stanza + RSMSet rsmSet = new RSMSet(null, "", -1, -1, null, 1, null, -1); + DataForm dataForm = getNewMamForm(); + addWithJid(chat.getUser().getJid(), dataForm); + MamQueryIQ mamQueryIQ = new MamQueryIQ(queryID, null, dataForm); + mamQueryIQ.setType(IQ.Type.set); + mamQueryIQ.setTo((Jid) null); + mamQueryIQ.addExtension(rsmSet); + accountItem.getConnection().sendStanza(mamQueryIQ); + return null; + } + }); + } + + /** Request messages after archivedID from chat history + * Else request messages after archivedID from all history */ + private @Nullable MamManager.MamQueryResult requestMessagesFromId( + @Nonnull AccountItem accountItem, @Nullable final AbstractChat chat, final String archivedId) { + + return requestToMessageArchive(accountItem, new MamRequest() { + @Override + MamManager.MamQueryResult execute(MamManager manager) throws Exception { + if (chat != null) return manager.pageAfter(chat.getUser().getJid(), archivedId, 50); + else return manager.pageAfter(null, archivedId, 50); + } + }); + } + + /** Request messages before archivedID from chat history */ + private @Nullable MamManager.MamQueryResult requestMessagesBeforeId( + @Nonnull AccountItem accountItem, @Nonnull final AbstractChat chat, final String archivedId) { + + return requestToMessageArchive(accountItem, new MamRequest() { + @Override + MamManager.MamQueryResult execute(MamManager manager) throws Exception { + return manager.pageBefore(chat.getUser().getJid(), archivedId, 50); + } + }); + } + + /** Request messages started with startID and ending with endID from chat history */ + private @Nullable MamManager.MamQueryResult requestMissedMessages( + @Nonnull AccountItem accountItem, @Nonnull final AbstractChat chat, + final Date startDate, final Date endDate) { + + return requestToMessageArchive(accountItem, new MamRequest() { + @Override + MamManager.MamQueryResult execute(MamManager manager) throws Exception { + return manager.queryArchive(50, startDate, endDate, chat.getUser().getJid(), null); + } + }); + } + + /** Request update archiving preferences on server */ + private void requestUpdatePreferences(@Nonnull final AccountItem accountItem) { + requestToMessageArchive(accountItem, new MamRequest() { + @Override + MamManager.MamPrefsResult execute(MamManager manager) throws Exception { + return manager.updateArchivingPreferences(null, null, accountItem.getMamDefaultBehaviour()); + } + }); + } + + /** Request archiving preferences from server */ + private @Nullable MamManager.MamPrefsResult requestPreferencesFromServer(@Nonnull final AccountItem accountItem) { + return requestToMessageArchive(accountItem, new MamRequest() { + @Override + MamManager.MamPrefsResult execute(MamManager manager) throws Exception { + return manager.retrieveArchivingPreferences(); + } + }); + } + + /** PARSING */ + + private void parseAndSaveMessageFromMamResult(Realm realm, AccountJid account,Forwarded forwarded) { + Stanza stanza = forwarded.getForwardedStanza(); + AccountItem accountItem = AccountManager.getInstance().getAccount(account); + Jid user = stanza.getFrom().asBareJid(); + if (user.equals(account.getFullJid().asBareJid())) + user = stanza.getTo().asBareJid(); + + try { + AbstractChat chat = MessageManager.getInstance().getOrCreateChat(account, UserJid.from(user)); + MessageItem messageItem = parseMessage(accountItem, account, chat.getUser(), forwarded, null); + if (messageItem != null) { + saveOrUpdateMessages(realm, Collections.singletonList(messageItem), true); + updateLastMessageId(chat, realm); + } + } catch (UserJid.UserJidCreateException e) { + LogManager.d(LOG_TAG, e.toString()); + } + } + + private List parseMessage(AccountItem accountItem, AccountJid account, UserJid user, + List forwardedMessages, String prevID) { + List messageItems = new ArrayList<>(); + String lastOutgoingId = null; + for (Forwarded forwarded : forwardedMessages) { + MessageItem message = parseMessage(accountItem, account, user, forwarded, prevID); + if (message != null) { + messageItems.add(message); + prevID = message.getArchivedId(); + if (!message.isIncoming()) lastOutgoingId = message.getUniqueId(); + } + } + + // mark messages before outgoing as read + if (lastOutgoingId != null) { + for (MessageItem message : messageItems) { + if (lastOutgoingId.equals(message.getUniqueId())) { + break; + } + message.setRead(true); + } + } + + return messageItems; + } + + private @Nullable MessageItem parseMessage(AccountItem accountItem, AccountJid account, UserJid user, Forwarded forwarded, String prevID) { + if (!(forwarded.getForwardedStanza() instanceof Message)) { + return null; + } + + Message message = (Message) forwarded.getForwardedStanza(); + + DelayInformation delayInformation = forwarded.getDelayInformation(); + + DelayInformation messageDelay = DelayInformation.from(message); + + String body = message.getBody(); + net.java.otr4j.io.messages.AbstractMessage otrMessage; + try { + otrMessage = SerializationUtils.toMessage(body); + } catch (IOException e) { + return null; + } + boolean encrypted = false; + if (otrMessage != null) { + if (otrMessage.messageType != net.java.otr4j.io.messages.AbstractMessage.MESSAGE_PLAINTEXT) { + encrypted = true; + try { + // this transforming just decrypt message if have keys. No action as injectMessage or something else + body = OTRManager.getInstance().transformReceivingIfSessionExist(account, user, body); + if (OTRManager.getInstance().isEncrypted(body)) { + return null; + } + } catch (Exception e) { + return null; + } + } + else body = ((PlainTextMessage) otrMessage).cleanText; + } + + // forward comment (to support previous forwarded xep) + String forwardComment = ForwardManager.parseForwardComment(message); + if (forwardComment != null) body = forwardComment; + + // modify body with references + Pair bodies = ReferencesManager.modifyBodyWithReferences(message, body); + body = bodies.first; + String markupBody = bodies.second; + + boolean incoming = message.getFrom().asBareJid().equals(user.getJid().asBareJid()); + + String uid = UUID.randomUUID().toString(); + MessageItem messageItem = new MessageItem(uid); + messageItem.setPreviousId(prevID); + + String archivedId = ArchivedHelper.getArchivedId(forwarded.getForwardedStanza()); + if (archivedId != null) messageItem.setArchivedId(archivedId); + + long timestamp = delayInformation.getStamp().getTime(); + + messageItem.setAccount(account); + messageItem.setUser(user); + messageItem.setResource(user.getJid().getResourceOrNull()); + messageItem.setText(body); + if (markupBody != null) messageItem.setMarkupText(markupBody); + messageItem.setTimestamp(timestamp); + if (messageDelay != null) { + messageItem.setDelayTimestamp(messageDelay.getStamp().getTime()); + } + messageItem.setIncoming(incoming); + messageItem.setStanzaId(AbstractChat.getStanzaId(message)); + messageItem.setPacketId(message.getStanzaId()); + messageItem.setReceivedFromMessageArchive(true); + messageItem.setRead(timestamp <= accountItem.getStartHistoryTimestamp()); + messageItem.setSent(true); + messageItem.setEncrypted(encrypted); + + // attachments + FileManager.processFileMessage(messageItem); + + RealmList attachments = HttpFileUploadManager.parseFileMessage(message); + if (attachments.size() > 0) + messageItem.setAttachments(attachments); + + // forwarded + messageItem.setOriginalStanza(message.toXML().toString()); + messageItem.setOriginalFrom(message.getFrom().toString()); + + return messageItem; + } + + /** SAVING */ + + private List saveOrUpdateMessages(Realm realm, final Collection messages) { + return saveOrUpdateMessages(realm, messages, false); + } + + private List saveOrUpdateMessages(Realm realm, final Collection messages, boolean ui) { + List messagesToSave = new ArrayList<>(); + if (messages != null && !messages.isEmpty()) { + Iterator iterator = messages.iterator(); + while (iterator.hasNext()) { + MessageItem newMessage = determineSaveOrUpdate(realm, iterator.next(), ui); + if (newMessage != null) messagesToSave.add(newMessage); + } + } + realm.beginTransaction(); + realm.copyToRealmOrUpdate(messagesToSave); + realm.commitTransaction(); + SyncManager.getInstance().onMessageSaved(); + EventBus.getDefault().post(new NewMessageEvent()); + return messagesToSave; + } + + private MessageItem determineSaveOrUpdate(Realm realm, final MessageItem message, boolean ui) { + Message originalMessage = null; + try { + originalMessage = (Message) PacketParserUtils.parseStanza(message.getOriginalStanza()); + } catch (Exception e) { + e.printStackTrace(); + } + + AbstractChat chat = MessageManager.getInstance().getOrCreateChat(message.getAccount(), message.getUser()); + if (chat == null) return null; + + MessageItem localMessage = findSameLocalMessage(realm, chat, message); + if (localMessage == null) { + // forwarded + if (originalMessage != null) { + RealmList forwardIds = chat.parseForwardedMessage(ui, originalMessage, message.getUniqueId()); + if (forwardIds != null && !forwardIds.isEmpty()) + message.setForwardedIds(forwardIds); + } + + // notify about new message + chat.enableNotificationsIfNeed(); + boolean notify = !message.isRead() && (message.getText() != null && !message.getText().trim().isEmpty()) + && message.isIncoming() && chat.notifyAboutMessage(); + boolean visible = MessageManager.getInstance().isVisibleChat(chat); + if (notify && !visible) + NotificationManager.getInstance().onMessageNotification(message); + // + + return message; + } else { + realm.beginTransaction(); + localMessage.setArchivedId(message.getArchivedId()); + realm.commitTransaction(); + return localMessage; + } + } + + /** UTILS */ + + private static DataForm getNewMamForm() { + FormField field = new FormField(FormField.FORM_TYPE); + field.setType(FormField.Type.hidden); + field.addValue(MamElements.NAMESPACE); + DataForm form = new DataForm(DataForm.Type.submit); + form.addField(field); + return form; + } + + private static void addWithJid(Jid withJid, DataForm dataForm) { + if (withJid == null) return; + FormField formField = new FormField("with"); + formField.addValue(withJid.toString()); + dataForm.addField(formField); + } + + private String getNextId(MamManager.MamQueryResult queryResult) { + String archivedId = null; + if (queryResult.forwardedMessages != null && !queryResult.forwardedMessages.isEmpty()) { + Forwarded forwarded = queryResult.forwardedMessages.get(queryResult.forwardedMessages.size() - 1); + archivedId = ArchivedHelper.getArchivedId(forwarded.getForwardedStanza()); + } + return archivedId; + } + + private Date getNextDate(MamManager.MamQueryResult queryResult) { + Date date = null; + if (queryResult.forwardedMessages != null && !queryResult.forwardedMessages.isEmpty()) { + Forwarded forwarded = queryResult.forwardedMessages.get(queryResult.forwardedMessages.size() - 1); + DelayInformation delayInformation = forwarded.getDelayInformation(); + date = new Date(delayInformation.getStamp().getTime() + 1); + } + return date; + } + + @Nullable private List findMissedMessages(Realm realm, AbstractChat chat) { + RealmResults results = realm.where(MessageItem.class) + .equalTo(MessageItem.Fields.ACCOUNT, chat.getAccount().toString()) + .equalTo(MessageItem.Fields.USER, chat.getUser().toString()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .isNotNull(MessageItem.Fields.ARCHIVED_ID) + .isNull(MessageItem.Fields.PREVIOUS_ID) + .findAllSorted(MessageItem.Fields.TIMESTAMP, Sort.DESCENDING); + + if (results != null && !results.isEmpty()) { + return new ArrayList<>(results); + } else return null; + } + + private MessageItem getMessageForCloseMissedMessages(Realm realm, MessageItem messageItem) { + RealmResults results = realm.where(MessageItem.class) + .equalTo(MessageItem.Fields.ACCOUNT, messageItem.getAccount().toString()) + .equalTo(MessageItem.Fields.USER, messageItem.getUser().toString()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .isNotNull(MessageItem.Fields.ARCHIVED_ID) + .lessThan(MessageItem.Fields.TIMESTAMP, messageItem.getTimestamp()) + .findAllSorted(MessageItem.Fields.TIMESTAMP, Sort.DESCENDING); + + if (results != null && !results.isEmpty()) { + return results.first(); + } else return null; + } + + private boolean isNeedMigration(AccountItem account, Realm realm) { + MessageItem result = realm.where(MessageItem.class) + .equalTo(MessageItem.Fields.ACCOUNT, account.getAccount().toString()) + .notEqualTo(MessageItem.Fields.PREVIOUS_ID, "legacy") + .findFirst(); + return result == null; + } + + private boolean historyIsNotEnough(Realm realm, AbstractChat chat) { + RealmResults results = realm.where(MessageItem.class) + .equalTo(MessageItem.Fields.ACCOUNT, chat.getAccount().toString()) + .equalTo(MessageItem.Fields.USER, chat.getUser().toString()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .findAll(); + return results.size() < 30; + } + + private String getLastMessageArchivedId(AccountItem account, Realm realm) { + RealmResults results = realm.where(MessageItem.class) + .equalTo(MessageItem.Fields.ACCOUNT, account.getAccount().toString()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .isNotNull(MessageItem.Fields.ARCHIVED_ID) + .findAllSorted(MessageItem.Fields.TIMESTAMP, Sort.ASCENDING); + + if (results != null && !results.isEmpty()) { + MessageItem lastMessage = results.last(); + return lastMessage.getArchivedId(); + } else return null; + } + + private MessageItem getFirstMessage(AbstractChat chat, Realm realm) { + RealmResults results = realm.where(MessageItem.class) + .equalTo(MessageItem.Fields.ACCOUNT, chat.getAccount().toString()) + .equalTo(MessageItem.Fields.USER, chat.getUser().toString()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .isNotNull(MessageItem.Fields.ARCHIVED_ID) + .findAllSorted(MessageItem.Fields.TIMESTAMP, Sort.ASCENDING); + + if (results != null && !results.isEmpty()) { + return results.first(); + } else return null; + } + + private MessageItem getFirstMessageForMigration(AbstractChat chat, Realm realm) { + RealmResults results = realm.where(MessageItem.class) + .equalTo(MessageItem.Fields.ACCOUNT, chat.getAccount().toString()) + .equalTo(MessageItem.Fields.USER, chat.getUser().toString()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .findAllSorted(MessageItem.Fields.TIMESTAMP, Sort.ASCENDING); + + if (results != null && !results.isEmpty()) { + return results.first(); + } else return null; + } + + private long getLastMessageTimestamp(AccountItem account, Realm realm) { + RealmResults results = realm.where(MessageItem.class) + .equalTo(MessageItem.Fields.ACCOUNT, account.getAccount().toString()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .findAllSorted(MessageItem.Fields.TIMESTAMP, Sort.ASCENDING); + + if (results != null && !results.isEmpty()) { + MessageItem lastMessage = results.last(); + return lastMessage.getTimestamp(); + } else return 0; + } + + private void updateLastMessageId(AbstractChat chat, Realm realm) { + RealmResults results = realm.where(MessageItem.class) + .equalTo(MessageItem.Fields.ACCOUNT, chat.getAccount().toString()) + .equalTo(MessageItem.Fields.USER, chat.getUser().toString()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .findAllSorted(MessageItem.Fields.TIMESTAMP, Sort.ASCENDING); + + if (results != null && !results.isEmpty()) { + MessageItem lastMessage = results.last(); + String id = lastMessage.getArchivedId(); + if (id == null) id = lastMessage.getStanzaId(); + chat.setLastMessageId(id); + } + } + + private MessageItem findSameLocalMessage(Realm realm, AbstractChat chat, MessageItem message) { + return realm.where(MessageItem.class) + .equalTo(MessageItem.Fields.ACCOUNT, chat.getAccount().toString()) + .equalTo(MessageItem.Fields.USER, chat.getUser().toString()) + .equalTo(MessageItem.Fields.TEXT, message.getText()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .equalTo(MessageItem.Fields.STANZA_ID, message.getStanzaId()) + .or() + .equalTo(MessageItem.Fields.ACCOUNT, chat.getAccount().toString()) + .equalTo(MessageItem.Fields.USER, chat.getUser().toString()) + .equalTo(MessageItem.Fields.TEXT, message.getText()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .equalTo(MessageItem.Fields.STANZA_ID, message.getPacketId()) + .or() + .equalTo(MessageItem.Fields.ACCOUNT, chat.getAccount().toString()) + .equalTo(MessageItem.Fields.USER, chat.getUser().toString()) + .equalTo(MessageItem.Fields.TEXT, message.getText()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .equalTo(MessageItem.Fields.STANZA_ID, message.getArchivedId()) + .or() + .equalTo(MessageItem.Fields.ACCOUNT, chat.getAccount().toString()) + .equalTo(MessageItem.Fields.USER, chat.getUser().toString()) + .equalTo(MessageItem.Fields.TEXT, message.getText()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .equalTo(MessageItem.Fields.ARCHIVED_ID, message.getArchivedId()) + .findFirst(); + } + + private void runMigrationToNewArchive(AccountItem accountItem, Realm realm) { + LogManager.d(LOG_TAG, "run migration for account: " + accountItem.getAccount().toString()); + Collection contacts = RosterManager.getInstance() + .getAccountRosterContacts(accountItem.getAccount()); + + for (RosterContact contact : contacts) { + AbstractChat chat = MessageManager.getInstance() + .getOrCreateChat(contact.getAccount(), contact.getUser()); + + MessageItem firstMessage = getFirstMessageForMigration(chat, realm); + SyncInfo syncInfo = realm.where(SyncInfo.class) + .equalTo(SyncInfo.FIELD_ACCOUNT, accountItem.getAccount().toString()) + .equalTo(SyncInfo.FIELD_USER, chat.getUser().toString()).findFirst(); + + if (firstMessage != null && syncInfo != null) { + realm.beginTransaction(); + firstMessage.setArchivedId(syncInfo.getFirstMamMessageMamId()); + firstMessage.setPreviousId(null); + realm.commitTransaction(); + } + } + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/extension/muc/RoomChat.java b/xabber/src/main/java/com/xabber/android/data/extension/muc/RoomChat.java index 4ecf78e22a..e3e0fa4e06 100644 --- a/xabber/src/main/java/com/xabber/android/data/extension/muc/RoomChat.java +++ b/xabber/src/main/java/com/xabber/android/data/extension/muc/RoomChat.java @@ -16,6 +16,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.util.Pair; import com.xabber.android.R; import com.xabber.android.data.Application; @@ -29,13 +30,13 @@ import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.entity.UserJid; import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager; +import com.xabber.android.data.extension.references.ReferencesManager; import com.xabber.android.data.message.AbstractChat; import com.xabber.android.data.message.ChatAction; import com.xabber.android.data.message.ForwardManager; import com.xabber.android.data.message.NewIncomingMessageEvent; import com.xabber.android.data.message.chat.ChatManager; import com.xabber.android.data.roster.RosterManager; -import com.xabber.xmpp.sid.UniqStanzaHelper; import org.greenrobot.eventbus.EventBus; import org.jivesoftware.smack.packet.Message; @@ -189,7 +190,7 @@ void putInvite(String packetID, UserJid user) { @Override protected MessageItem createNewMessageItem(String text) { - return createMessageItem(nickname, text, null, null, false, + return createMessageItem(nickname, text, null, null, null, false, false, false, false, UUID.randomUUID().toString(), null, null, null, account.getFullJid().toString(), null, true); } @@ -259,28 +260,25 @@ protected boolean onPacket(UserJid bareAddress, Stanza stanza, boolean isCarbons newAction(resource, subject, ChatAction.subject, true); } else { boolean notify = true; - String stanzaId = message.getStanzaId(); - // Use stanza id from XEP-0359 if common stanza id is null - if (stanzaId == null) stanzaId = UniqStanzaHelper.getStanzaId(message); - - DelayInformation delayInformation = DelayInformation.from(message); - Date delay = null; - if (delayInformation != null) { - delay = delayInformation.getStamp(); - } + Date delay = getDelayStamp(message); if (delay != null) { notify = false; } - // forward comment + // forward comment (to support previous forwarded xep) String forwardComment = ForwardManager.parseForwardComment(stanza); if (forwardComment != null) text = forwardComment; + // modify body with references + Pair bodies = ReferencesManager.modifyBodyWithReferences(message, text); + text = bodies.first; + String markupText = bodies.second; + String originalFrom = stanza.getFrom().toString(); - String messageUId = getMessageIdIfInHistory(stanzaId, text); + String messageUId = getMessageIdIfInHistory(getStanzaId(message), text); if (messageUId != null) { if (isSelf(resource)) { markMessageAsDelivered(messageUId, originalFrom); @@ -302,13 +300,15 @@ protected boolean onPacket(UserJid bareAddress, Stanza stanza, boolean isCarbons // create message with file-attachments if (attachments.size() > 0) - createAndSaveFileMessage(true, uid, resource, text, null, delay, true, notify, - false, false, stanzaId, attachments, + createAndSaveFileMessage(true, uid, resource, text, markupText, null, + null, delay, true, notify, + false, false, getStanzaId(message), attachments, originalStanza, null, originalFrom, true, false); // create message without attachments - else createAndSaveNewMessage(true, uid, resource, text, null, delay, true, notify, - false, false, stanzaId, + else createAndSaveNewMessage(true, uid, resource, text, markupText, null, + null, delay, true, notify, + false, false, getStanzaId(message), originalStanza, null, originalFrom, forwardIds, true, false); EventBus.getDefault().post(new NewIncomingMessageEvent(account, user)); @@ -371,7 +371,7 @@ else createAndSaveNewMessage(true, uid, resource, text, null, delay, true, notif } @Override - protected String parseInnerMessage(boolean ui, Message message, String parentMessageId) { + protected String parseInnerMessage(boolean ui, Message message, Date timestamp, String parentMessageId) { if (message.getType() == Message.Type.error) return null; final org.jxmpp.jid.Jid from = message.getFrom(); @@ -382,11 +382,6 @@ protected String parseInnerMessage(boolean ui, Message message, String parentMes if (text == null) return null; if (subject != null) return null; - String stanzaId = message.getStanzaId(); - - // Use stanza id from XEP-0359 if common stanza id is null - if (stanzaId == null) stanzaId = UniqStanzaHelper.getStanzaId(message); - RealmList attachments = HttpFileUploadManager.parseFileMessage(message); String uid = UUID.randomUUID().toString(); @@ -394,18 +389,25 @@ protected String parseInnerMessage(boolean ui, Message message, String parentMes String originalStanza = message.toXML().toString(); String originalFrom = message.getFrom().toString(); boolean fromMUC = message.getType().equals(Type.groupchat); + + // forward comment (to support previous forwarded xep) String forwardComment = ForwardManager.parseForwardComment(message); if (forwardComment != null) text = forwardComment; + // modify body with references + Pair bodies = ReferencesManager.modifyBodyWithReferences(message, text); + text = bodies.first; + String markupText = bodies.second; + // create message with file-attachments if (attachments.size() > 0) - createAndSaveFileMessage(ui, uid, resource, text, null, null, - true, false, false, false, stanzaId, attachments, + createAndSaveFileMessage(ui, uid, resource, text, markupText, null, timestamp, getDelayStamp(message), + true, false, false, false, getStanzaId(message), attachments, originalStanza, parentMessageId, originalFrom, fromMUC, true); // create message without attachments - else createAndSaveNewMessage(ui, uid, resource, text, null, null, - true, false, false, false, stanzaId, + else createAndSaveNewMessage(ui, uid, resource, text, markupText, null, timestamp, getDelayStamp(message), + true, false, false, false, getStanzaId(message), originalStanza, parentMessageId, originalFrom, forwardIds, fromMUC, true); return uid; @@ -474,8 +476,8 @@ private void onAvailable(Resourcepart resource) { if (isRequested()) { if (showStatusChange()) { createAndSaveNewMessage(true, UUID.randomUUID().toString(), resource, Application.getInstance().getString( - R.string.action_join_complete_to, user), - ChatAction.complete, null, true, true, + R.string.action_join_complete_to, user), null, + ChatAction.complete, null, null, true, true, false, false, null, null, null, null, null, true, false); } diff --git a/xabber/src/main/java/com/xabber/android/data/extension/muc/RoomContact.java b/xabber/src/main/java/com/xabber/android/data/extension/muc/RoomContact.java index 303ff95194..443d9e4eef 100644 --- a/xabber/src/main/java/com/xabber/android/data/extension/muc/RoomContact.java +++ b/xabber/src/main/java/com/xabber/android/data/extension/muc/RoomContact.java @@ -48,11 +48,6 @@ public StatusMode getStatusMode() { @Override public Drawable getAvatar() { - return AvatarManager.getInstance().getRoomAvatar(user); - } - - @Override - public Drawable getAvatarForContactList() { return AvatarManager.getInstance().getRoomAvatarForContactList(user); } diff --git a/xabber/src/main/java/com/xabber/android/data/extension/references/Forward.java b/xabber/src/main/java/com/xabber/android/data/extension/references/Forward.java new file mode 100644 index 0000000000..d5662b292a --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/extension/references/Forward.java @@ -0,0 +1,31 @@ +package com.xabber.android.data.extension.references; + +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.forward.packet.Forwarded; + +import java.util.List; + +public class Forward extends ReferenceElement { + private final List forwarded; + + public Forward(int begin, int end, List forwarded) { + super(begin, end); + this.forwarded = forwarded; + } + + @Override + public Type getType() { + return Type.forward; + } + + @Override + public void appendToXML(XmlStringBuilder xml) { + for (Forwarded forward : forwarded) { + xml.append(forward.toXML()); + } + } + + public List getForwarded() { + return forwarded; + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/extension/references/Markup.java b/xabber/src/main/java/com/xabber/android/data/extension/references/Markup.java new file mode 100644 index 0000000000..b4fd485e5f --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/extension/references/Markup.java @@ -0,0 +1,56 @@ +package com.xabber.android.data.extension.references; + +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class Markup extends ReferenceElement { + private final boolean bold; + private final boolean italic; + private final boolean underline; + private final boolean strike; + private final String uri; + + public Markup(int begin, int end, boolean bold, boolean italic, boolean underline, boolean strike, String uri) { + super(begin, end); + this.bold = bold; + this.italic = italic; + this.underline = underline; + this.strike = strike; + this.uri = uri; + } + + @Override + public Type getType() { + return Type.markup; + } + + @Override + public void appendToXML(XmlStringBuilder xml) { + if (bold) xml.emptyElement(ELEMENT_BOLD); + if (italic) xml.emptyElement(ELEMENT_ITALIC); + if (underline) xml.emptyElement(ELEMENT_UNDERLINE); + if (strike) xml.emptyElement(ELEMENT_STRIKE); + if (uri != null && uri.isEmpty()) { + xml.element(ELEMENT_URI, uri); + } + } + + public boolean isBold() { + return bold; + } + + public boolean isItalic() { + return italic; + } + + public boolean isUnderline() { + return underline; + } + + public boolean isStrike() { + return strike; + } + + public String getUri() { + return uri; + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/extension/references/Media.java b/xabber/src/main/java/com/xabber/android/data/extension/references/Media.java new file mode 100644 index 0000000000..0227dada9d --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/extension/references/Media.java @@ -0,0 +1,30 @@ +package com.xabber.android.data.extension.references; + +import org.jivesoftware.smack.util.XmlStringBuilder; + +import java.util.List; + +public class Media extends ReferenceElement { + private final List media; + + public Media(int begin, int end, List media) { + super(begin, end); + this.media = media; + } + + @Override + public Type getType() { + return Type.media; + } + + @Override + public void appendToXML(XmlStringBuilder xml) { + for (RefMedia item : media) { + xml.append(item.toXML()); + } + } + + public List getMedia() { + return media; + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/extension/references/Mention.java b/xabber/src/main/java/com/xabber/android/data/extension/references/Mention.java new file mode 100644 index 0000000000..6c4d872c9b --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/extension/references/Mention.java @@ -0,0 +1,28 @@ +package com.xabber.android.data.extension.references; + +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class Mention extends ReferenceElement { + private final String uri; // jid + + public Mention(int begin, int end, String uri) { + super(begin, end); + this.uri = uri; + } + + @Override + public Type getType() { + return Type.mention; + } + + @Override + public void appendToXML(XmlStringBuilder xml) { + if (uri != null && uri.isEmpty()) { + xml.element(ELEMENT_URI, uri); + } + } + + public String getUri() { + return uri; + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/extension/references/Quote.java b/xabber/src/main/java/com/xabber/android/data/extension/references/Quote.java new file mode 100644 index 0000000000..b59358c9a0 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/extension/references/Quote.java @@ -0,0 +1,28 @@ +package com.xabber.android.data.extension.references; + +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class Quote extends ReferenceElement { + private final String marker; + + public Quote(int begin, int end, String marker) { + super(begin, end); + this.marker = marker; + } + + @Override + public Type getType() { + return Type.quote; + } + + @Override + public void appendToXML(XmlStringBuilder xml) { + if (marker != null && !marker.isEmpty()) { + xml.element(ELEMENT_MARKER, marker); + } + } + + public String getMarker() { + return marker; + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/extension/references/RefFile.java b/xabber/src/main/java/com/xabber/android/data/extension/references/RefFile.java new file mode 100644 index 0000000000..835e56f6b3 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/extension/references/RefFile.java @@ -0,0 +1,142 @@ +package com.xabber.android.data.extension.references; + +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class RefFile { + + public static final String ELEMENT = "file"; + + public static final String ELEMENT_MEDIA_TYPE = "media-type"; + public static final String ELEMENT_NAME = "name"; + public static final String ELEMENT_DESC = "desc"; + public static final String ELEMENT_HEIGHT = "height"; + public static final String ELEMENT_WIDTH = "width"; + public static final String ELEMENT_SIZE = "size"; + public static final String ELEMENT_DURATION = "duration"; + + private String mediaType; + private String name; + private String desc; + private int height; + private int width; + private long size; + private long duration; + + public String getMediaType() { + return mediaType; + } + + public String getName() { + return name; + } + + public String getDesc() { + return desc; + } + + public int getHeight() { + return height; + } + + public int getWidth() { + return width; + } + + public long getSize() { + return size; + } + + public long getDuration() { + return duration; + } + + public CharSequence toXML() { + XmlStringBuilder xml = new XmlStringBuilder(); + xml.openElement(ELEMENT); + if (getMediaType() != null && !getMediaType().isEmpty()) { + xml.openElement(ELEMENT_MEDIA_TYPE); + xml.append(getMediaType()); + xml.closeElement(ELEMENT_MEDIA_TYPE); + } + if (getName() != null && !getName().isEmpty()) { + xml.openElement(ELEMENT_NAME); + xml.append(getName()); + xml.closeElement(ELEMENT_NAME); + } + if (getHeight() > 0) { + xml.openElement(ELEMENT_HEIGHT); + xml.append(String.valueOf(getHeight())); + xml.closeElement(ELEMENT_HEIGHT); + } + if (getWidth() > 0) { + xml.openElement(ELEMENT_WIDTH); + xml.append(String.valueOf(getWidth())); + xml.closeElement(ELEMENT_WIDTH); + } + if (getSize() > 0) { + xml.openElement(ELEMENT_SIZE); + xml.append(String.valueOf(getSize())); + xml.closeElement(ELEMENT_SIZE); + } + if (getDesc() != null && !getDesc().isEmpty()) { + xml.openElement(ELEMENT_DESC); + xml.append(getDesc()); + xml.closeElement(ELEMENT_DESC); + } + if (getDuration() > 0) { + xml.openElement(ELEMENT_DURATION); + xml.append(String.valueOf(getDuration())); + xml.closeElement(ELEMENT_DURATION); + } + xml.closeElement(ELEMENT); + return xml; + } + + public static Builder newBuilder() { + return new RefFile().new Builder(); + } + + public class Builder { + private Builder() {} + + public RefFile build() { + return RefFile.this; + } + + public Builder setMediaType(String mediaType) { + RefFile.this.mediaType = mediaType; + return this; + } + + public Builder setName(String name) { + RefFile.this.name = name; + return this; + } + + public Builder setDesc(String desc) { + RefFile.this.desc = desc; + return this; + } + + public Builder setHeight(int height) { + RefFile.this.height = height; + return this; + } + + public Builder setWidth(int width) { + RefFile.this.width = width; + return this; + } + + public Builder setSize(long size) { + RefFile.this.size = size; + return this; + } + + public Builder setDuration(long duration) { + RefFile.this.duration = duration; + return this; + } + } + +} diff --git a/xabber/src/main/java/com/xabber/android/data/extension/references/RefMedia.java b/xabber/src/main/java/com/xabber/android/data/extension/references/RefMedia.java new file mode 100644 index 0000000000..ccca515ffb --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/extension/references/RefMedia.java @@ -0,0 +1,40 @@ +package com.xabber.android.data.extension.references; + +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class RefMedia { + + public static final String ELEMENT = "media"; + public static final String ELEMENT_URI = "uri"; + + private RefFile file; + private String uri; + + public RefMedia(RefFile file, String uri) { + this.file = file; + this.uri = uri; + } + + public RefFile getFile() { + return file; + } + + public String getUri() { + return uri; + } + + public CharSequence toXML() { + XmlStringBuilder xml = new XmlStringBuilder(); + xml.openElement(ELEMENT); + if (file != null) { + xml.append(file.toXML()); + } + if (uri != null) { + xml.openElement(ELEMENT_URI); + xml.append(uri); + xml.closeElement(ELEMENT_URI); + } + xml.closeElement(ELEMENT); + return xml; + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/extension/references/ReferenceElement.java b/xabber/src/main/java/com/xabber/android/data/extension/references/ReferenceElement.java new file mode 100644 index 0000000000..e4c1d60d86 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/extension/references/ReferenceElement.java @@ -0,0 +1,71 @@ +package com.xabber.android.data.extension.references; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public abstract class ReferenceElement implements ExtensionElement { + + public static final String NAMESPACE = "urn:xmpp:reference:0"; + public static final String ELEMENT = "reference"; + public static final String ELEMENT_BOLD = "bold"; + public static final String ELEMENT_ITALIC = "italic"; + public static final String ELEMENT_UNDERLINE = "underline"; + public static final String ELEMENT_STRIKE = "strike"; + public static final String ELEMENT_URI = "uri"; + public static final String ELEMENT_MARKER = "marker"; + + public static final String ATTRIBUTE_TYPE = "type"; + public static final String ATTRIBUTE_BEGIN = "begin"; + public static final String ATTRIBUTE_END = "end"; + + public enum Type { + media, + forward, + markup, + mention, + quote, + voice + } + + protected final int begin; + protected final int end; + + public ReferenceElement(int begin, int end) { + this.begin = begin; + this.end = end; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public CharSequence toXML() { + XmlStringBuilder xml = new XmlStringBuilder(this); + xml.attribute(ATTRIBUTE_TYPE, getType()); + xml.attribute(ATTRIBUTE_BEGIN, begin); + xml.attribute(ATTRIBUTE_END, end); + xml.rightAngleBracket(); + appendToXML(xml); + xml.closeElement(this); + return xml; + } + + public void appendToXML(XmlStringBuilder xml) { } + + public int getBegin() { + return begin; + } + + public int getEnd() { + return end; + } + + public abstract Type getType(); +} \ No newline at end of file diff --git a/xabber/src/main/java/com/xabber/android/data/extension/references/ReferencesManager.java b/xabber/src/main/java/com/xabber/android/data/extension/references/ReferencesManager.java new file mode 100644 index 0000000000..4fb32d397b --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/extension/references/ReferencesManager.java @@ -0,0 +1,250 @@ +package com.xabber.android.data.extension.references; + +import android.text.Html; +import android.util.Pair; + +import com.xabber.android.data.database.messagerealm.Attachment; +import com.xabber.android.data.database.messagerealm.MessageItem; +import com.xabber.android.ui.text.ClickSpan; +import com.xabber.android.utils.Utils; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.delay.packet.DelayInformation; +import org.jivesoftware.smackx.forward.packet.Forwarded; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import javax.annotation.Nonnull; + +public class ReferencesManager { + + @Nonnull + public static List getForwardedFromReferences(Stanza packet) { + List elements = packet.getExtensions(ReferenceElement.ELEMENT, ReferenceElement.NAMESPACE); + if (elements == null || elements.size() == 0) return Collections.emptyList(); + + List forwarded = new ArrayList<>(); + for (ExtensionElement element : elements) { + if (element instanceof Forward) { + forwarded.addAll(((Forward) element).getForwarded()); + } + } + return forwarded; + } + + public static Media createMediaReferences(Attachment attachment, int begin, int end) { + List mediaList = new ArrayList<>(); + RefFile.Builder builder = RefFile.newBuilder(); + builder.setName(attachment.getTitle()); + builder.setMediaType(attachment.getMimeType()); + builder.setDuration(attachment.getDuration()); + builder.setSize(attachment.getFileSize()); + if (attachment.getImageHeight() != null) + builder.setHeight(attachment.getImageHeight()); + if (attachment.getImageWidth() != null) + builder.setWidth(attachment.getImageWidth()); + RefMedia media = new RefMedia(builder.build(), attachment.getFileUrl()); + mediaList.add(media); + + return new Media(begin, end, mediaList); + } + + public static Forward createForwardReference(MessageItem item, int begin, int end) { + List forwardedList = new ArrayList<>(); + try { + Message forwarded = PacketParserUtils.parseStanza(item.getOriginalStanza()); + forwardedList.add(new Forwarded(new DelayInformation(new Date(item.getTimestamp())), forwarded)); + } catch (Exception e) { + e.printStackTrace(); + } + + return new Forward(begin, end, forwardedList); + } + + @Nonnull + public static List getMediaFromReferences(Stanza packet) { + List elements = packet.getExtensions(ReferenceElement.ELEMENT, ReferenceElement.NAMESPACE); + if (elements == null || elements.size() == 0) return Collections.emptyList(); + + List media = new ArrayList<>(); + for (ExtensionElement element : elements) { + if (element instanceof Media) { + media.addAll(((Media) element).getMedia()); + } + } + return media; + } + + public static Pair modifyBodyWithReferences(Message message, String body) { + if (body == null || body.isEmpty() || body.trim().isEmpty()) return new Pair<>(body, null); + + List elements = message.getExtensions(ReferenceElement.ELEMENT, ReferenceElement.NAMESPACE); + if (elements == null || elements.size() == 0) return new Pair<>(body, null); + + List references = getReferences(elements); + if (references.isEmpty()) return new Pair<>(body, null); + + // encode HTML and split into chars + String[] chars = stringToChars(Utils.xmlEncode(body)); + + // modify chars with references except markup and mention + for (ReferenceElement reference : references) { + if (!(reference instanceof Markup) && !(reference instanceof Mention) && !(reference instanceof Quote)) + chars = modifyBodyWithReferences(chars, reference); + } + + // chars to string and decode from html + String regularBody = Html.fromHtml(charsToString(chars).replace("\n", "
")).toString(); + String markupBody = null; + + // modify chars with markup and mention references + for (ReferenceElement reference : references) { + if (reference instanceof Markup || reference instanceof Mention || reference instanceof Quote) + chars = modifyBodyWithReferences(chars, reference); + } + markupBody = charsToString(chars); + if (regularBody.equals(markupBody)) markupBody = null; + + return new Pair<>(regularBody, markupBody); + } + + + + private static List getReferences(List elements) { + List references = new ArrayList<>(); + for (ExtensionElement element : elements) { + if (element instanceof ReferenceElement) references.add((ReferenceElement) element); + } + return references; + } + + private static String charsToString(String[] array) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < array.length; i++) { + if (!array[i].equals(String.valueOf(Character.MIN_VALUE))) + builder.append(array[i]); + } + return builder.toString(); + } + + private static String[] stringToChars(String source) { + String[] result = new String[source.codePointCount(0, source.length())]; + int i = 0; + for (int offset = 0; offset < source.length(); ) { + int codepoint = source.codePointAt(offset); + result[i] = String.valueOf(Character.toChars(codepoint)); + offset += Character.charCount(codepoint); + i++; + } + return result; + } + + private static String[] modifyBodyWithReferences(String[] chars, ReferenceElement reference) { + int begin = reference.getBegin(); + if (begin < 0) begin = 0; + int end = reference.getEnd(); + if (end >= chars.length) end = chars.length - 1; + if (begin > end) return chars; + + switch (reference.getType()) { + case media: + chars = remove(begin, end, chars); + break; + case forward: + chars = remove(begin, end, chars); + break; + case markup: + chars = markup(begin, end, chars, (Markup) reference); + break; + case quote: + chars = quote(begin, end, chars, (Quote) reference); + break; + case mention: + chars = mention(begin, end, chars, (Mention) reference); + break; + } + return chars; + } + + private static String[] remove(int begin, int end, String[] source) { + for (int i = begin; i <= end; i++) { + source[i] = String.valueOf(Character.MIN_VALUE); + } + return source; + } + + private static String[] quote(int begin, int end, String[] source, Quote reference) { + int del = reference.getMarker().length(); + int removed = 0; + for (int i = begin; i <= end; i++) { + if (removed < del) { + if (removed == 0) source[i] = "\u2503 "; + else source[i] = String.valueOf(Character.MIN_VALUE); + removed++; + } + if (source[i].equals("\n")) removed = 0; + } + return source; + } + + private static String[] markup(int begin, int end, String[] source, Markup reference) { + StringBuilder builderOpen = new StringBuilder(); + StringBuilder builderClose = new StringBuilder(); + if (reference.isBold()) { + builderOpen.append(""); + builderClose.append(new StringBuilder("").reverse()); + } + if (reference.isItalic()) { + builderOpen.append(""); + builderClose.append(new StringBuilder("").reverse()); + } + if (reference.isUnderline()) { + builderOpen.append(""); + builderClose.append(new StringBuilder("").reverse()); + } + if (reference.isStrike()) { + builderOpen.append(""); + builderClose.append(new StringBuilder("").reverse()); + } + if (reference.getUri() != null && !reference.getUri().isEmpty()) { + // Add [‍] (zero-with-join) symbol before custom tag to avoid issue: + // https://stackoverflow.com/questions/23568481/weird-taghandler-behavior-detecting-opening-and-closing-tags + builderOpen.append("‍"); + builderClose.append(new StringBuilder("").reverse()); + } + source[begin] = builderOpen.append(source[begin]).toString(); + builderClose.append(new StringBuilder(source[end]).reverse()); + source[end] = builderClose.reverse().toString(); + return source; + } + + private static String[] mention(int begin, int end, String[] source, Mention reference) { + StringBuilder builderOpen = new StringBuilder(); + StringBuilder builderClose = new StringBuilder(); + if (reference.getUri() != null && !reference.getUri().isEmpty()) { + // Add [‍] (zero-with-join) symbol before custom tag to avoid issue: + // https://stackoverflow.com/questions/23568481/weird-taghandler-behavior-detecting-opening-and-closing-tags + builderOpen.append("‍"); + builderClose.append(new StringBuilder("").reverse()); + } + source[begin] = builderOpen.append(source[begin]).toString(); + builderClose.append(new StringBuilder(source[end]).reverse()); + source[end] = builderClose.reverse().toString(); + return source; + } + +} diff --git a/xabber/src/main/java/com/xabber/android/data/extension/references/ReferencesProvider.java b/xabber/src/main/java/com/xabber/android/data/extension/references/ReferencesProvider.java new file mode 100644 index 0000000000..fccd46e176 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/extension/references/ReferencesProvider.java @@ -0,0 +1,171 @@ +package com.xabber.android.data.extension.references; + +import com.xabber.android.data.log.LogManager; +import com.xabber.android.utils.Utils; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smackx.forward.packet.Forwarded; +import org.jivesoftware.smackx.forward.provider.ForwardedProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.List; + +public class ReferencesProvider extends ExtensionElementProvider { + + @Override + public ReferenceElement parse(XmlPullParser parser, int initialDepth) throws Exception { + String type = null, beginS = null, endS = null, marker = null, uri = null; + List forwardedMessages = new ArrayList<>(); + List mediaElements = new ArrayList<>(); + boolean bold = false, italic = false, underline = false, strike = false; + + outerloop: while (true) { + int eventType = parser.getEventType(); + switch (eventType) { + case XmlPullParser.START_TAG: + if (ReferenceElement.ELEMENT.equals(parser.getName()) + && ReferenceElement.NAMESPACE.equals(parser.getNamespace())) { + type = parser.getAttributeValue("", ReferenceElement.ATTRIBUTE_TYPE); + beginS = parser.getAttributeValue("", ReferenceElement.ATTRIBUTE_BEGIN); + endS = parser.getAttributeValue("", ReferenceElement.ATTRIBUTE_END); + parser.next(); + } else if (Forwarded.ELEMENT.equals(parser.getName()) + && Forwarded.NAMESPACE.equals(parser.getNamespace())) { + Forwarded forwarded = ForwardedProvider.INSTANCE.parse(parser); + if (forwarded != null) forwardedMessages.add(forwarded); + parser.next(); + } else if (RefMedia.ELEMENT.equals(parser.getName())) { + RefMedia media = parseMedia(parser); + if (media != null) mediaElements.add(media); + parser.next(); + } else if (ReferenceElement.ELEMENT_BOLD.equals(parser.getName())) { + bold = true; + parser.next(); + } else if (ReferenceElement.ELEMENT_ITALIC.equals(parser.getName())) { + italic = true; + parser.next(); + } else if (ReferenceElement.ELEMENT_UNDERLINE.equals(parser.getName())) { + underline = true; + parser.next(); + } else if (ReferenceElement.ELEMENT_STRIKE.equals(parser.getName())) { + strike = true; + parser.next(); + } else if (ReferenceElement.ELEMENT_URI.equals(parser.getName())) { + uri = parser.nextText(); + } else if (ReferenceElement.ELEMENT_MARKER.equals(parser.getName())) { + marker = Utils.xmlEncode(parser.nextText()); + } else parser.next(); + break; + case XmlPullParser.END_TAG: + if (ReferenceElement.ELEMENT.equals(parser.getName())) { + break outerloop; + } else parser.next(); + break; + default: + parser.next(); + } + } + + int begin = 0, end = 0; + if (beginS != null && !beginS.isEmpty()) begin = Integer.valueOf(beginS); + if (endS != null && !endS.isEmpty()) end = Integer.valueOf(endS); + + try { + ReferenceElement.Type refType = ReferenceElement.Type.valueOf(type); + switch (refType) { + case forward: + return new Forward(begin, end, forwardedMessages); + case media: + return new Media(begin, end, mediaElements); + case markup: + return new Markup(begin, end, bold, italic, underline, strike, uri); + case quote: + return new Quote(begin, end, marker); + case mention: + return new Mention(begin, end, uri); + default: + return null; + } + } catch (Exception e) { + LogManager.d(ReferencesProvider.class, e.toString()); + return null; + } + } + + private RefMedia parseMedia(XmlPullParser parser) throws Exception { + String uri = null; + RefFile file = null; + + parser.next(); + outerloop: while (true) { + int eventType = parser.getEventType(); + switch (eventType) { + case XmlPullParser.START_TAG: + if (RefFile.ELEMENT.equals(parser.getName())) { + file = parseFile(parser); + parser.next(); + } + if (RefMedia.ELEMENT_URI.equals(parser.getName())) { + uri = parser.nextText(); + } + break; + case XmlPullParser.END_TAG: + if (RefMedia.ELEMENT.equals(parser.getName())) { + break outerloop; + } else parser.next(); + break; + default: + parser.next(); + } + } + if (file != null && uri != null) return new RefMedia(file, uri); + else return null; + } + + private RefFile parseFile(XmlPullParser parser) throws Exception { + RefFile.Builder builder = RefFile.newBuilder(); + + parser.next(); + outerloop: while (true) { + int eventType = parser.getEventType(); + switch (eventType) { + case XmlPullParser.START_TAG: + switch (parser.getName()) { + case RefFile.ELEMENT_MEDIA_TYPE: + builder.setMediaType(parser.nextText()); + break; + case RefFile.ELEMENT_NAME: + builder.setName(parser.nextText()); + break; + case RefFile.ELEMENT_DESC: + builder.setDesc(parser.nextText()); + break; + case RefFile.ELEMENT_HEIGHT: + builder.setHeight(Integer.valueOf(parser.nextText())); + break; + case RefFile.ELEMENT_WIDTH: + builder.setWidth(Integer.valueOf(parser.nextText())); + break; + case RefFile.ELEMENT_SIZE: + builder.setSize(Long.valueOf(parser.nextText())); + break; + case RefFile.ELEMENT_DURATION: + builder.setDuration(Long.valueOf(parser.nextText())); + break; + default: + parser.next(); + } + break; + case XmlPullParser.END_TAG: + if (RefFile.ELEMENT.equals(parser.getName())) { + break outerloop; + } else parser.next(); + break; + default: + parser.next(); + } + } + return builder.build(); + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/extension/vcard/CustomVCardManager.java b/xabber/src/main/java/com/xabber/android/data/extension/vcard/CustomVCardManager.java index 10a8374c1b..7f7ce718fe 100644 --- a/xabber/src/main/java/com/xabber/android/data/extension/vcard/CustomVCardManager.java +++ b/xabber/src/main/java/com/xabber/android/data/extension/vcard/CustomVCardManager.java @@ -114,6 +114,12 @@ public VCard loadVCard(Jid jid) throws SmackException.NoResponseException, XMPPE return result; } + public void sendVCardRequest(Jid jid) throws SmackException.NotConnectedException, InterruptedException { + VCard vcardRequest = new VCard(); + vcardRequest.setTo(jid); + connection().sendStanza(vcardRequest); + } + /** * Returns true if the given entity understands the vCard-XML format and allows the exchange of such. * diff --git a/xabber/src/main/java/com/xabber/android/data/extension/vcard/VCardManager.java b/xabber/src/main/java/com/xabber/android/data/extension/vcard/VCardManager.java index 0f4d57ce75..71c182cc7d 100644 --- a/xabber/src/main/java/com/xabber/android/data/extension/vcard/VCardManager.java +++ b/xabber/src/main/java/com/xabber/android/data/extension/vcard/VCardManager.java @@ -47,7 +47,6 @@ import org.jivesoftware.smack.packet.IQ.Type; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Stanza; -import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smackx.vcardtemp.packet.VCard; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; @@ -151,16 +150,12 @@ public void onRosterReceived(AccountItem accountItem) { } } - Collection blockedContacts = BlockingManager.getInstance().getBlockedContacts(account); - Collection accountRosterContacts = RosterManager.getInstance().getAccountRosterContacts(account); // Request vCards for new contacts. for (RosterContact contact : accountRosterContacts) { if (!names.containsKey(contact.getUser().getJid())) { - if (!blockedContacts.contains(contact.getUser())) { - request(account, contact.getUser().getJid()); - } + request(account, contact.getUser().getJid()); } } } @@ -239,13 +234,13 @@ void onVCardReceived(final AccountJid account, final Jid bareAddress, final VCar name = new StructuredName(vCard.getNickName(), vCard.getField(VCardProperty.FN.name()), vCard.getFirstName(), vCard.getMiddleName(), vCard.getLastName()); - try { - if (account.getFullJid().asBareJid().equals(bareAddress.asBareJid())) { - PresenceManager.getInstance().resendPresence(account); - } - } catch (NetworkException e) { - LogManager.exception(this, e); - } +// try { +// if (account.getFullJid().asBareJid().equals(bareAddress.asBareJid())) { +// PresenceManager.getInstance().resendPresence(account); +// } +// } catch (NetworkException e) { +// LogManager.exception(this, e); +// } } names.put(bareAddress, name); @@ -325,6 +320,12 @@ public void onStanza(ConnectionItem connection, Stanza stanza) { } } } + + if (stanza instanceof VCard) { + Jid from = stanza.getFrom(); + if (from == null) return; + onVCardReceived(account, from, (VCard) stanza); + } } @SuppressWarnings("WeakerAccess") @@ -343,8 +344,6 @@ void getVCard(final AccountJid account, final Jid srcUser) { return; } - VCard vCard = null; - Collection blockedContacts = BlockingManager.getInstance().getBlockedContacts(account); for (UserJid blockedContact : blockedContacts) { if (blockedContact.getBareJid().equals(srcUser.asBareJid())) { @@ -357,17 +356,10 @@ void getVCard(final AccountJid account, final Jid srcUser) { if (entityBareJid != null) { vCardRequests.add(srcUser); try { - vCard = vCardManager.loadVCard(srcUser); - } catch (SmackException.NoResponseException | SmackException.NotConnectedException e) { + vCardManager.sendVCardRequest(srcUser); + } catch (SmackException.NotConnectedException e) { LogManager.exception(this, e); LogManager.w(this, "Error getting vCard: " + e.getMessage()); - } catch (XMPPException.XMPPErrorException e ) { - LogManager.exception(this, e); - LogManager.w(this, "XMPP error getting vCard: " + e.getMessage() + e.getXMPPError()); - - if (e.getXMPPError().getCondition() == XMPPError.Condition.item_not_found) { - vCard = new VCard(); - } } catch (ClassCastException e) { LogManager.exception(this, e); @@ -379,18 +371,6 @@ void getVCard(final AccountJid account, final Jid srcUser) { } vCardRequests.remove(srcUser); } - - final VCard finalVCard = vCard; - Application.getInstance().runOnUiThread(new Runnable() { - @Override - public void run() { - if (finalVCard == null) { - onVCardFailed(account, srcUser); - } else { - onVCardReceived(account, srcUser, finalVCard); - } - } - }); } public void saveVCard(final AccountJid account, final VCard vCard) { diff --git a/xabber/src/main/java/com/xabber/android/data/http/CrowdfundingManager.java b/xabber/src/main/java/com/xabber/android/data/http/CrowdfundingManager.java index 3a7d7826ba..cdbc26bf16 100644 --- a/xabber/src/main/java/com/xabber/android/data/http/CrowdfundingManager.java +++ b/xabber/src/main/java/com/xabber/android/data/http/CrowdfundingManager.java @@ -49,7 +49,7 @@ public void onLoad() { else if (isTimeToCrowdfunding() && isLeaderCacheExpired()) requestLeader(); } else if (!CrowdfundingManager.getInstance().haveDelayedMessages() && isCacheExpired()) - requestFeed(lastMessage.getReceivedTimestamp()); + requestFeed(lastMessage.getTimestamp()); } private void requestLeader() { @@ -75,7 +75,7 @@ public void startUpdateTimer(final int delay, final int step) { if (timer != null) timer.cancel(); if (!CrowdfundingManager.getInstance().haveDelayedMessages()) { CrowdfundingMessage lastMessage = getLastMessageFromRealm(); - if (lastMessage != null && isCacheExpired()) requestFeed(lastMessage.getReceivedTimestamp()); + if (lastMessage != null && isCacheExpired()) requestFeed(lastMessage.getTimestamp()); return; } @@ -129,8 +129,9 @@ private CrowdfundingMessage messageToRealm(CrowdfundingClient.Message message) { realmMessage.setRead(false); realmMessage.setDelay(message.getDelay()); realmMessage.setLeader(message.isLeader()); - realmMessage.setTimestamp(message.getTimestamp()); - realmMessage.setReceivedTimestamp(getCurrentTime()); + if (message.isLeader()) + realmMessage.setTimestamp(getCurrentTime()); + else realmMessage.setTimestamp(message.getTimestamp()); realmMessage.setAuthorAvatar(message.getAuthor().getAvatar()); realmMessage.setAuthorJid(message.getAuthor().getJabberId()); @@ -150,15 +151,15 @@ private CrowdfundingMessage messageToRealm(CrowdfundingClient.Message message) { public boolean haveDelayedMessages() { Realm realm = RealmManager.getInstance().getNewRealm(); CrowdfundingMessage message = realm.where(CrowdfundingMessage.class) - .notEqualTo("delay", 0).findFirst(); + .notEqualTo(CrowdfundingMessage.Fields.DELAY, 0).findFirst(); return message != null; } public RealmResults getMessagesWithDelay(int delay) { Realm realm = RealmManager.getInstance().getNewRealm(); return realm.where(CrowdfundingMessage.class) - .lessThanOrEqualTo("delay", delay) - .findAllSorted("timestamp"); + .lessThanOrEqualTo(CrowdfundingMessage.Fields.DELAY, delay) + .findAllSorted(CrowdfundingMessage.Fields.TIMESTAMP); } public void removeDelay(int delay) { @@ -168,7 +169,7 @@ public void removeDelay(int delay) { for (CrowdfundingMessage message : messages) { // remove delay and update received time message.setDelay(0); - message.setReceivedTimestamp(getCurrentTime()); + message.setTimestamp(getCurrentTime()); } realm.commitTransaction(); realm.close(); @@ -176,7 +177,8 @@ public void removeDelay(int delay) { public CrowdfundingMessage getLastMessageFromRealm() { Realm realm = RealmManager.getInstance().getNewRealm(); - RealmResults messages = realm.where(CrowdfundingMessage.class).findAllSorted("timestamp"); + RealmResults messages = realm.where(CrowdfundingMessage.class) + .findAllSorted(CrowdfundingMessage.Fields.TIMESTAMP); if (messages != null && !messages.isEmpty()) return messages.last(); else return null; } @@ -184,23 +186,23 @@ public CrowdfundingMessage getLastMessageFromRealm() { public CrowdfundingMessage getLastNotDelayedMessageFromRealm() { Realm realm = RealmManager.getInstance().getNewRealm(); RealmResults messages = realm.where(CrowdfundingMessage.class) - .equalTo("delay", 0) - .findAllSorted("timestamp"); + .equalTo(CrowdfundingMessage.Fields.DELAY, 0) + .findAllSorted(CrowdfundingMessage.Fields.TIMESTAMP); if (messages != null && !messages.isEmpty()) return messages.last(); else return null; } public int getUnreadMessageCount() { Realm realm = RealmManager.getInstance().getNewRealm(); - Long count = realm.where(CrowdfundingMessage.class).equalTo("read", false) - .equalTo("delay", 0).count(); + Long count = realm.where(CrowdfundingMessage.class).equalTo(CrowdfundingMessage.Fields.READ, false) + .equalTo(CrowdfundingMessage.Fields.DELAY, 0).count(); return count.intValue(); } public Observable> getUnreadMessageCountAsObservable() { Realm realm = RealmManager.getInstance().getNewRealm(); - return realm.where(CrowdfundingMessage.class).equalTo("read", false) - .equalTo("delay", 0).findAll().asObservable(); + return realm.where(CrowdfundingMessage.class).equalTo(CrowdfundingMessage.Fields.READ, false) + .equalTo(CrowdfundingMessage.Fields.DELAY, 0).findAll().asObservable(); } public void reloadMessages() { @@ -214,14 +216,14 @@ public void reloadMessages() { /** Ignore business rules. Use only for debug */ public void fetchFeedForDebug() { CrowdfundingMessage lastMessage = getLastMessageFromRealm(); - if (lastMessage != null) requestFeed(lastMessage.getReceivedTimestamp()); + if (lastMessage != null) requestFeed(lastMessage.getTimestamp()); } public void markMessagesAsRead(String[] ids) { if (ids.length == 0) return; Realm realm = RealmManager.getInstance().getNewRealm(); RealmResults messages = realm.where(CrowdfundingMessage.class) - .equalTo("read", false).in("id", ids).findAll(); + .equalTo(CrowdfundingMessage.Fields.READ, false).in(CrowdfundingMessage.Fields.ID, ids).findAll(); realm.beginTransaction(); for (CrowdfundingMessage message : messages) { @@ -232,7 +234,7 @@ public void markMessagesAsRead(String[] ids) { private void removeAllMessages() { Realm realm = RealmManager.getInstance().getNewRealm(); - RealmResults messages = realm.where(CrowdfundingMessage.class).findAllSorted("timestamp"); + RealmResults messages = realm.where(CrowdfundingMessage.class).findAll(); realm.beginTransaction(); for (CrowdfundingMessage message : messages) message.deleteFromRealm(); diff --git a/xabber/src/main/java/com/xabber/android/data/http/IPushApi.java b/xabber/src/main/java/com/xabber/android/data/http/IPushApi.java new file mode 100644 index 0000000000..f73d5dfb38 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/http/IPushApi.java @@ -0,0 +1,18 @@ +package com.xabber.android.data.http; + +import okhttp3.ResponseBody; +import retrofit2.http.Body; +import retrofit2.http.HTTP; +import retrofit2.http.Header; +import retrofit2.http.POST; +import rx.Single; + +public interface IPushApi { + + @POST("jid/endpoints/") + Single registerEndpoint(@Header("Authorization") String key, @Body PushApiClient.Endpoint endpoint); + + @HTTP(method = "DELETE", path = "jid/endpoints/", hasBody = true) + Single deleteEndpoint(@Header("Authorization") String key, @Body PushApiClient.Endpoint endpoint); + +} diff --git a/xabber/src/main/java/com/xabber/android/data/http/PushApiClient.java b/xabber/src/main/java/com/xabber/android/data/http/PushApiClient.java new file mode 100644 index 0000000000..c847282b15 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/http/PushApiClient.java @@ -0,0 +1,50 @@ +package com.xabber.android.data.http; + +import com.xabber.android.R; +import com.xabber.android.data.Application; +import com.xabber.android.data.xaccount.HttpApiManager; + +import okhttp3.ResponseBody; +import rx.Single; + +public class PushApiClient { + + private static final String DEFAULT_PUSH_PROVIDER = "fcm"; + + private static PushApiClient instance; + + public static PushApiClient getInstance() { + if (instance == null) + instance = new PushApiClient(); + return instance; + } + + public static Single registerEndpoint(String endpoint, String jid) { + if (getAPIKey().length() < 36) return Single.error(new Throwable("API key not provided")); + return HttpApiManager.getPushApi().registerEndpoint(getAPIKey(), + new Endpoint(endpoint, DEFAULT_PUSH_PROVIDER, jid)); + } + + public static Single deleteEndpoint(String endpoint, String jid) { + if (getAPIKey().length() < 36) return Single.error(new Throwable("API key not provided")); + return HttpApiManager.getPushApi().deleteEndpoint(getAPIKey(), + new Endpoint(endpoint, DEFAULT_PUSH_PROVIDER, jid)); + } + + private static String getAPIKey() { + return "Key " + Application.getInstance().getResources().getString(R.string.PUSH_API_KEY); + } + + public static class Endpoint { + final String endpoint_key; + final String provider; + final String target; + + public Endpoint(String endpoint_key, String provider, String target) { + this.endpoint_key = endpoint_key; + this.provider = provider; + this.target = target; + } + } + +} diff --git a/xabber/src/main/java/com/xabber/android/data/message/AbstractChat.java b/xabber/src/main/java/com/xabber/android/data/message/AbstractChat.java index 57d1974d96..d32ea0927a 100644 --- a/xabber/src/main/java/com/xabber/android/data/message/AbstractChat.java +++ b/xabber/src/main/java/com/xabber/android/data/message/AbstractChat.java @@ -17,9 +17,7 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.util.Log; -import com.xabber.android.R; import com.xabber.android.data.Application; import com.xabber.android.data.NetworkException; import com.xabber.android.data.SettingsManager; @@ -28,7 +26,6 @@ import com.xabber.android.data.database.messagerealm.Attachment; import com.xabber.android.data.database.messagerealm.ForwardId; import com.xabber.android.data.database.messagerealm.MessageItem; -import com.xabber.android.data.database.messagerealm.SyncInfo; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.entity.BaseEntity; import com.xabber.android.data.entity.UserJid; @@ -37,32 +34,34 @@ import com.xabber.android.data.extension.cs.ChatStateManager; import com.xabber.android.data.extension.file.FileManager; import com.xabber.android.data.extension.file.UriUtils; -import com.xabber.android.data.extension.forward.ForwardComment; -import com.xabber.android.data.extension.httpfileupload.ExtendedFormField; import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager; import com.xabber.android.data.extension.muc.MUCManager; import com.xabber.android.data.extension.otr.OTRManager; +import com.xabber.android.data.extension.references.ReferenceElement; +import com.xabber.android.data.extension.references.ReferencesManager; import com.xabber.android.data.log.LogManager; import com.xabber.android.data.message.chat.ChatManager; import com.xabber.android.data.notification.MessageNotificationManager; import com.xabber.android.data.notification.NotificationManager; +import com.xabber.android.data.roster.RosterCacheManager; +import com.xabber.android.utils.Utils; +import com.xabber.xmpp.sid.OriginIdElement; +import com.xabber.xmpp.sid.UniqStanzaHelper; import org.greenrobot.eventbus.EventBus; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.StanzaListener; -import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message.Type; import org.jivesoftware.smack.packet.Stanza; -import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.delay.packet.DelayInformation; import org.jivesoftware.smackx.forward.packet.Forwarded; -import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.Jid; import org.jxmpp.jid.parts.Resourcepart; import java.io.File; +import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -119,9 +118,11 @@ public abstract class AbstractChat extends BaseEntity implements RealmChangeList private boolean isRemotePreviousHistoryCompletelyLoaded = false; private Date lastSyncedTime; - private RealmResults syncInfo; private MessageItem lastMessage; private RealmResults messages; + private String lastMessageId = null; + private boolean historyIsFull = false; + private boolean historyRequestedAtStart = false; protected AbstractChat(@NonNull final AccountJid account, @NonNull final UserJid user, boolean isPrivateMucChat) { super(account, isPrivateMucChat ? user : user.getBareUserJid()); @@ -198,18 +199,6 @@ public RealmResults getMessages() { return messages; } - public RealmResults getSyncInfo() { - if (syncInfo == null) { - syncInfo = MessageDatabaseManager.getInstance() - .getRealmUiThread().where(SyncInfo.class) - .equalTo(SyncInfo.FIELD_ACCOUNT, getAccountString()) - .equalTo(SyncInfo.FIELD_USER, getUserString()) - .findAllAsync(); - } - - return syncInfo; - } - boolean isStatusTrackingEnabled() { return trackStatus; } @@ -246,7 +235,7 @@ public boolean notifyAboutMessage() { else return false; } - private void enableNotificationsIfNeed() { + public void enableNotificationsIfNeed() { int currentTime = (int) (System.currentTimeMillis() / 1000L); NotificationState.NotificationMode mode = notificationState.getMode(); @@ -272,8 +261,8 @@ private void enableNotificationsIfNeed() { * @param text can be null. */ public void newAction(Resourcepart resource, String text, ChatAction action, boolean fromMUC) { - createAndSaveNewMessage(true, UUID.randomUUID().toString(), resource, text, action, - null, true, false, false, false, + createAndSaveNewMessage(true, UUID.randomUUID().toString(), resource, text, null, + action, null, null, true, false, false, false, null, null, null, null, null, fromMUC, false); } @@ -292,28 +281,30 @@ public void newAction(Resourcepart resource, String text, ChatAction action, boo * @param offline Whether message was received from server side offline storage. * @return */ - protected void createAndSaveNewMessage(boolean ui, String uid, Resourcepart resource, String text, final ChatAction action, + protected void createAndSaveNewMessage(boolean ui, String uid, Resourcepart resource, String text, + String markupText, final ChatAction action, final Date timestamp, final Date delayTimestamp, final boolean incoming, boolean notify, final boolean encrypted, final boolean offline, final String stanzaId, final String originalStanza, final String parentMessageId, final String originalFrom, final RealmList forwardIds, boolean fromMUC, boolean fromMAM) { - final MessageItem messageItem = createMessageItem(uid, resource, text, action, delayTimestamp, - incoming, notify, encrypted, offline, stanzaId, null, + final MessageItem messageItem = createMessageItem(uid, resource, text, markupText, action, + timestamp, delayTimestamp, incoming, notify, encrypted, offline, stanzaId, null, originalStanza, parentMessageId, originalFrom, forwardIds, fromMUC, fromMAM); saveMessageItem(ui, messageItem); //EventBus.getDefault().post(new NewMessageEvent()); } - protected void createAndSaveFileMessage(boolean ui, String uid, Resourcepart resource, String text, final ChatAction action, + protected void createAndSaveFileMessage(boolean ui, String uid, Resourcepart resource, String text, + String markupText, final ChatAction action, final Date timestamp, final Date delayTimestamp, final boolean incoming, boolean notify, final boolean encrypted, final boolean offline, final String stanzaId, RealmList attachments, final String originalStanza, final String parentMessageId, final String originalFrom, boolean fromMUC, boolean fromMAM) { - final MessageItem messageItem = createMessageItem(uid, resource, text, action, delayTimestamp, - incoming, notify, encrypted, offline, stanzaId, attachments, + final MessageItem messageItem = createMessageItem(uid, resource, text, markupText, action, + timestamp, delayTimestamp, incoming, notify, encrypted, offline, stanzaId, attachments, originalStanza, parentMessageId, originalFrom, null, fromMUC, fromMAM); saveMessageItem(ui, messageItem); @@ -337,18 +328,20 @@ public void execute(Realm realm) { } } - protected MessageItem createMessageItem(Resourcepart resource, String text, ChatAction action, + protected MessageItem createMessageItem(Resourcepart resource, String text, + String markupText, ChatAction action, Date delayTimestamp, boolean incoming, boolean notify, boolean encrypted, boolean offline, String stanzaId, RealmList attachments, String originalStanza, String parentMessageId, String originalFrom, RealmList forwardIds, boolean fromMUC) { - return createMessageItem(UUID.randomUUID().toString(), resource, text, action, - delayTimestamp, incoming, notify, encrypted, offline, stanzaId, attachments, + return createMessageItem(UUID.randomUUID().toString(), resource, text, markupText, action, + null, delayTimestamp, incoming, notify, encrypted, offline, stanzaId, attachments, originalStanza, parentMessageId, originalFrom, forwardIds, fromMUC, false); } - protected MessageItem createMessageItem(String uid, Resourcepart resource, String text, ChatAction action, + protected MessageItem createMessageItem(String uid, Resourcepart resource, String text, + String markupText, ChatAction action, Date timestamp, Date delayTimestamp, boolean incoming, boolean notify, boolean encrypted, boolean offline, String stanzaId, RealmList attachments, String originalStanza, String parentMessageId, String originalFrom, @@ -368,7 +361,7 @@ protected MessageItem createMessageItem(String uid, Resourcepart resource, Strin send = true; } - final Date timestamp = new Date(); + if (timestamp == null) timestamp = new Date(); if (text.trim().isEmpty()) { notify = false; @@ -402,6 +395,7 @@ protected MessageItem createMessageItem(String uid, Resourcepart resource, Strin messageItem.setAction(action.toString()); } messageItem.setText(text); + if (markupText != null) messageItem.setMarkupText(markupText); messageItem.setTimestamp(timestamp.getTime()); if (delayTimestamp != null) { messageItem.setDelayTimestamp(delayTimestamp.getTime()); @@ -434,6 +428,12 @@ protected MessageItem createMessageItem(String uid, Resourcepart resource, Strin if (this.notifyAboutMessage()) this.archived = false; + // update last id in chat + messageItem.setPreviousId(getLastMessageId()); + String id = messageItem.getArchivedId(); + if (id == null) id = messageItem.getStanzaId(); + setLastMessageId(id); + return messageItem; } @@ -514,22 +514,25 @@ public synchronized MessageItem getLastMessage() { return lastMessage; } + public void setLastMessage(MessageItem lastMessage) { + this.lastMessage = lastMessage; + } + private void updateLastMessage() { - if (messages.isValid() && messages.isLoaded() && !messages.isEmpty()) { - List textMessages = MessageDatabaseManager.getInstance() - .getRealmUiThread() - .copyFromRealm(messages.where().isNull(MessageItem.Fields.ACTION) - .or().equalTo(MessageItem.Fields.ACTION, ChatAction.available.toString()).findAll()); - - if (!textMessages.isEmpty()) - lastMessage = textMessages.get(textMessages.size() - 1); - else - lastMessage = MessageDatabaseManager.getInstance() - .getRealmUiThread() - .copyFromRealm(messages.last()); - } else { - lastMessage = null; - } + Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread(); + lastMessage = realm.where(MessageItem.class) + .equalTo(MessageItem.Fields.ACCOUNT, account.toString()) + .equalTo(MessageItem.Fields.USER, user.toString()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .isNotNull(MessageItem.Fields.TEXT) + .isNull(MessageItem.Fields.ACTION) + .or() + .equalTo(MessageItem.Fields.ACCOUNT, account.toString()) + .equalTo(MessageItem.Fields.USER, user.toString()) + .isNull(MessageItem.Fields.PARENT_MESSAGE_ID) + .isNotNull(MessageItem.Fields.TEXT) + .equalTo(MessageItem.Fields.ACTION, ChatAction.available.toString()) + .findAllSorted(MessageItem.Fields.TIMESTAMP, Sort.ASCENDING).last(null); } /** @@ -563,7 +566,7 @@ public Message createMessagePacket(String body) { } /** - * Send stanza with XEP-0221 + * Send stanza with data-references. */ public Message createFileMessagePacket(String stanzaId, RealmList attachments, String body) { @@ -573,31 +576,27 @@ public Message createFileMessagePacket(String stanzaId, RealmList at message.setThread(threadId); if (stanzaId != null) message.setStanzaId(stanzaId); - DataForm dataForm = new DataForm(DataForm.Type.form); - - int i = 1; + StringBuilder builder = new StringBuilder(body); for (Attachment attachment : attachments) { - ExtendedFormField formField = new ExtendedFormField("media" + i); - i++; - formField.setLabel(attachment.getTitle()); - - ExtendedFormField.Uri uri = new ExtendedFormField.Uri(attachment.getMimeType(), attachment.getFileUrl()); - uri.setSize(attachment.getFileSize()); - uri.setDuration(attachment.getDuration()); - - formField.setMedia( - new ExtendedFormField.Media(String.valueOf(attachment.getImageHeight()), - String.valueOf(attachment.getImageWidth()), uri)); - - dataForm.addField(formField); + StringBuilder rowBuilder = new StringBuilder(); + if (builder.length() > 0) rowBuilder.append("\n"); + rowBuilder.append(attachment.getFileUrl()); + + int begin = getSizeOfEncodedChars(builder.toString()); + builder.append(rowBuilder); + ReferenceElement reference = ReferencesManager.createMediaReferences(attachment, + begin, getSizeOfEncodedChars(builder.toString()) - 1); + message.addExtension(reference); } - message.addExtension(dataForm); - message.setBody(body); - Log.d("XEP-0221", message.toXML().toString()); + message.setBody(builder); return message; } + private int getSizeOfEncodedChars(String str) { + return Utils.xmlEncode(str).toCharArray().length; + } + /** * Prepare text to be send. * @@ -661,36 +660,28 @@ boolean sendMessage(MessageItem messageItem) { messageItem.getAttachments(), text); } else if (messageItem.haveForwardedMessages()) { - - int count = messageItem.getForwardedIds().size(); - String body = String.format(Application.getInstance().getResources() - .getString(R.string.forwarded_support_text), count); - if (text != null && !text.isEmpty()) body += "\n" + text; - - message = createMessagePacket(body, messageItem.getStanzaId()); - Realm realm = MessageDatabaseManager.getInstance().getNewBackgroundRealm(); + RealmResults items = realm.where(MessageItem.class) + .in(MessageItem.Fields.UNIQUE_ID, messageItem.getForwardedIdsAsArray()).findAll(); - // forwarded - if (messageItem.getForwardedIds() != null && messageItem.getForwardedIds().size() > 0) { - final String[] ids = new String[messageItem.getForwardedIds().size()]; - int i = 0; - for (ForwardId id : messageItem.getForwardedIds()) { - ids[i] = id.getForwardMessageId(); - i++; - } - - RealmResults items = realm.where(MessageItem.class) - .in(MessageItem.Fields.UNIQUE_ID, ids).findAll(); + List references = new ArrayList<>(); + StringBuilder builder = new StringBuilder(); + if (items != null && !items.isEmpty()) { for (MessageItem item : items) { - try { - Message forwarded = (Message) PacketParserUtils.parseStanza(item.getOriginalStanza()); - message.addExtension(new Forwarded(new DelayInformation(new Date(item.getTimestamp())), forwarded)); - } catch (Exception e) { - e.printStackTrace(); - } + String forward = ClipManager.createMessageTree(realm, item.getUniqueId()) + "\n"; + int begin = getSizeOfEncodedChars(builder.toString()); + builder.append(forward); + ReferenceElement reference = ReferencesManager.createForwardReference(item, + begin, getSizeOfEncodedChars(builder.toString()) - 1); + references.add(reference); } - message.addExtension(new ForwardComment(text)); + } + builder.append(text); + text = builder.toString(); + + message = createMessagePacket(text, messageItem.getStanzaId()); + for (ReferenceElement element : references) { + message.addExtension(element); } } else if (text != null) { @@ -700,6 +691,7 @@ boolean sendMessage(MessageItem messageItem) { if (message != null) { ChatStateManager.getInstance().updateOutgoingMessage(AbstractChat.this, message); CarbonManager.getInstance().updateOutgoingMessage(AbstractChat.this, message); + message.addExtension(new OriginIdElement(messageItem.getStanzaId())); if (delayTimestamp != null) { message.addExtension(new DelayInformation(delayTimestamp)); } @@ -786,6 +778,7 @@ protected void onComplete() { * Disconnection occured. */ protected void onDisconnect() { + setLastMessageId(null); } public void setIsPrivateMucChatAccepted(boolean isPrivateMucChatAccepted) { @@ -803,6 +796,8 @@ boolean isPrivateMucChatAccepted() { @Override public void onChange(RealmResults messageItems) { updateLastMessage(); + RosterCacheManager.saveLastMessageToContact( + MessageDatabaseManager.getInstance().getRealmUiThread(), lastMessage); } /** UNREAD MESSAGES */ @@ -926,20 +921,68 @@ public void setLastPosition(int lastPosition) { } public RealmList parseForwardedMessage(boolean ui, Stanza packet, String parentMessageId) { - List elements = packet.getExtensions(Forwarded.ELEMENT, Forwarded.NAMESPACE); - if (elements == null || elements.size() == 0) return null; - - RealmList forwarded = new RealmList<>(); - for (ExtensionElement element : elements) { - if (element instanceof Forwarded) { - Stanza stanza = ((Forwarded) element).getForwardedStanza(); - if (stanza instanceof Message) { - forwarded.add(new ForwardId(parseInnerMessage(ui, (Message) stanza, parentMessageId))); - } + List forwarded = ReferencesManager.getForwardedFromReferences(packet); + if (forwarded.isEmpty()) forwarded = ForwardManager.getForwardedFromStanza(packet); + if (forwarded.isEmpty()) return null; + + RealmList forwardedIds = new RealmList<>(); + for (Forwarded forward : forwarded) { + Stanza stanza = forward.getForwardedStanza(); + DelayInformation delayInformation = forward.getDelayInformation(); + Date timestamp = delayInformation.getStamp(); + if (stanza instanceof Message) { + forwardedIds.add(new ForwardId(parseInnerMessage(ui, (Message) stanza, timestamp, parentMessageId))); } } - return forwarded; + return forwardedIds; + } + + protected abstract String parseInnerMessage(boolean ui, Message message, Date timestamp, String parentMessageId); + + public String getLastMessageId() { + return lastMessageId; + } + + public void setLastMessageId(String lastMessageId) { + this.lastMessageId = lastMessageId; + } + + public boolean historyIsFull() { + return historyIsFull; + } + + public void setHistoryIsFull() { + this.historyIsFull = true; } - protected abstract String parseInnerMessage(boolean ui, Message message, String parentMessageId); + public boolean isHistoryRequestedAtStart() { + return historyRequestedAtStart; + } + + public void setHistoryRequestedAtStart(boolean needSaveToRealm) { + this.historyRequestedAtStart = true; + if (needSaveToRealm) ChatManager.getInstance().saveOrUpdateChatDataToRealm(this); + } + + public static String getStanzaId(Message message) { + String stanzaId = null; + + stanzaId = UniqStanzaHelper.getOriginId(message); + if (stanzaId != null && !stanzaId.isEmpty()) return stanzaId; + + stanzaId = UniqStanzaHelper.getStanzaId(message); + if (stanzaId != null && !stanzaId.isEmpty()) return stanzaId; + + stanzaId = message.getStanzaId(); + return stanzaId; + } + + public static Date getDelayStamp(Message message) { + DelayInformation delayInformation = DelayInformation.from(message); + if (delayInformation != null) { + return delayInformation.getStamp(); + } else { + return null; + } + } } \ No newline at end of file diff --git a/xabber/src/main/java/com/xabber/android/data/message/BackpressureMessageSaver.java b/xabber/src/main/java/com/xabber/android/data/message/BackpressureMessageSaver.java index 8e07d4e1d3..c77cba9e68 100644 --- a/xabber/src/main/java/com/xabber/android/data/message/BackpressureMessageSaver.java +++ b/xabber/src/main/java/com/xabber/android/data/message/BackpressureMessageSaver.java @@ -3,6 +3,7 @@ import com.xabber.android.data.database.MessageDatabaseManager; import com.xabber.android.data.database.messagerealm.MessageItem; import com.xabber.android.data.log.LogManager; +import com.xabber.android.data.push.SyncManager; import org.greenrobot.eventbus.EventBus; @@ -42,6 +43,7 @@ private BackpressureMessageSaver() { private void createSubject() { subject = PublishSubject.create(); subject.buffer(500, TimeUnit.MILLISECONDS) + .onBackpressureBuffer(50) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1>() { @Override @@ -58,6 +60,7 @@ public void execute(Realm realm) { @Override public void onSuccess() { EventBus.getDefault().post(new NewMessageEvent()); + SyncManager.getInstance().onMessageSaved(); } }); } catch (Exception e) { diff --git a/xabber/src/main/java/com/xabber/android/data/message/ChatData.java b/xabber/src/main/java/com/xabber/android/data/message/ChatData.java index de5884abad..4155c8528d 100644 --- a/xabber/src/main/java/com/xabber/android/data/message/ChatData.java +++ b/xabber/src/main/java/com/xabber/android/data/message/ChatData.java @@ -12,15 +12,18 @@ public class ChatData { private boolean archived; private NotificationState notificationState; private int lastPosition; + private boolean historyRequestedAtStart; public ChatData(String subject, String accountJid, String userJid, - boolean archived, NotificationState notificationState, int lastPosition) { + boolean archived, NotificationState notificationState, int lastPosition, + boolean historyRequestedAtStart) { this.subject = subject; this.accountJid = accountJid; this.userJid = userJid; this.archived = archived; this.notificationState = notificationState; this.lastPosition = lastPosition; + this.historyRequestedAtStart = historyRequestedAtStart; } public String getSubject() { @@ -70,4 +73,8 @@ public int getLastPosition() { public void setLastPosition(int lastPosition) { this.lastPosition = lastPosition; } + + public boolean isHistoryRequestedAtStart() { + return historyRequestedAtStart; + } } diff --git a/xabber/src/main/java/com/xabber/android/data/message/ClipManager.java b/xabber/src/main/java/com/xabber/android/data/message/ClipManager.java index 6d619422c3..01a92c415d 100644 --- a/xabber/src/main/java/com/xabber/android/data/message/ClipManager.java +++ b/xabber/src/main/java/com/xabber/android/data/message/ClipManager.java @@ -5,6 +5,7 @@ import com.xabber.android.data.Application; import com.xabber.android.data.database.MessageDatabaseManager; +import com.xabber.android.data.database.messagerealm.Attachment; import com.xabber.android.data.database.messagerealm.MessageItem; import com.xabber.android.data.roster.RosterManager; import com.xabber.android.utils.StringUtils; @@ -32,6 +33,11 @@ public void run() { }); } + public static String createMessageTree(Realm realm, String id) { + String[] str = {id}; + return messagesToText(realm, str, 1); + } + private static void insertDataToClipboard(final String text) { Application.getInstance().runOnUiThread(new Runnable() { @Override @@ -89,6 +95,14 @@ private static String messageToText(Realm realm, MessageItem message, stringBuilder.append("\n"); } + if (message.haveAttachments()) { + for (Attachment attachment : message.getAttachments()) { + stringBuilder.append(space); + stringBuilder.append(attachment.getFileUrl()); + stringBuilder.append("\n"); + } + } + if (!message.getText().isEmpty()) { stringBuilder.append(space); stringBuilder.append(message.getText()); diff --git a/xabber/src/main/java/com/xabber/android/data/message/CrowdfundingChat.java b/xabber/src/main/java/com/xabber/android/data/message/CrowdfundingChat.java index 9637deacba..38afd58418 100644 --- a/xabber/src/main/java/com/xabber/android/data/message/CrowdfundingChat.java +++ b/xabber/src/main/java/com/xabber/android/data/message/CrowdfundingChat.java @@ -42,7 +42,7 @@ public static CrowdfundingChat createCrowdfundingChat(int unreadCount, Crowdfund public Date getLastTime() { if (lastMessage != null) - return new Date((long) lastMessage.getReceivedTimestamp() * 1000); + return new Date((long) lastMessage.getTimestamp() * 1000); return new Date(0); } @@ -56,7 +56,7 @@ public CrowdfundingMessage getLastCrowdMessage() { } @Override - protected String parseInnerMessage(boolean ui, Message message, String parentMessageId) { + protected String parseInnerMessage(boolean ui, Message message, Date timestamp, String parentMessageId) { return null; } diff --git a/xabber/src/main/java/com/xabber/android/data/message/ForwardManager.java b/xabber/src/main/java/com/xabber/android/data/message/ForwardManager.java index 3b259bd9f9..5f1a06927b 100644 --- a/xabber/src/main/java/com/xabber/android/data/message/ForwardManager.java +++ b/xabber/src/main/java/com/xabber/android/data/message/ForwardManager.java @@ -10,9 +10,14 @@ import org.greenrobot.eventbus.EventBus; import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smackx.forward.packet.Forwarded; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import javax.annotation.Nonnull; + import io.realm.Realm; import io.realm.RealmList; @@ -49,4 +54,18 @@ public static String parseForwardComment(Stanza packet) { return null; } + @Nonnull + public static List getForwardedFromStanza(Stanza packet) { + List elements = packet.getExtensions(Forwarded.ELEMENT, Forwarded.NAMESPACE); + if (elements == null || elements.size() == 0) return Collections.emptyList(); + + List forwarded = new ArrayList<>(); + for (ExtensionElement element : elements) { + if (element instanceof Forwarded) { + forwarded.add((Forwarded)element); + } + } + return forwarded; + } + } diff --git a/xabber/src/main/java/com/xabber/android/data/message/MessageManager.java b/xabber/src/main/java/com/xabber/android/data/message/MessageManager.java index 4c5774ef8d..3a6a081dd0 100644 --- a/xabber/src/main/java/com/xabber/android/data/message/MessageManager.java +++ b/xabber/src/main/java/com/xabber/android/data/message/MessageManager.java @@ -18,6 +18,7 @@ import android.os.Environment; import android.os.Looper; import android.support.annotation.Nullable; +import android.util.Pair; import com.xabber.android.R; import com.xabber.android.data.Application; @@ -49,6 +50,7 @@ import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager; import com.xabber.android.data.extension.muc.MUCManager; import com.xabber.android.data.extension.muc.RoomChat; +import com.xabber.android.data.extension.references.ReferencesManager; import com.xabber.android.data.log.LogManager; import com.xabber.android.data.message.chat.ChatManager; import com.xabber.android.data.message.chat.MucPrivateChatNotification; @@ -77,8 +79,8 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.UUID; import io.realm.Realm; @@ -170,7 +172,12 @@ public AbstractChat getChat(AccountJid account, UserJid user) { public Collection getChatsOfEnabledAccount() { List chats = new ArrayList<>(); - for (AccountJid accountJid : AccountManager.getInstance().getEnabledAccounts()) { + + HashSet enabledAccounts = new HashSet<>(); + enabledAccounts.addAll(AccountManager.getInstance().getEnabledAccounts()); + enabledAccounts.addAll(AccountManager.getInstance().getCachedEnabledAccounts()); + + for (AccountJid accountJid : enabledAccounts) { chats.addAll(this.chats.getNested(accountJid.toString()).values()); } return chats; @@ -204,6 +211,7 @@ private RegularChat createChat(AccountJid account, UserJid user) { chat.setLastPosition(chatData.getLastPosition()); chat.setArchived(chatData.isArchived(), false); chat.setNotificationState(chatData.getNotificationState(), false); + if (chatData.isHistoryRequestedAtStart()) chat.setHistoryRequestedAtStart(false); } addChat(chat); return chat; @@ -216,6 +224,7 @@ private RegularChat createPrivateMucChat(AccountJid account, FullJid fullJid) th chat.setLastPosition(chatData.getLastPosition()); chat.setArchived(chatData.isArchived(), false); chat.setNotificationState(chatData.getNotificationState(), false); + if (chatData.isHistoryRequestedAtStart()) chat.setHistoryRequestedAtStart(false); } addChat(chat); return chat; @@ -320,13 +329,7 @@ public void execute(Realm realm) { attachment.setFileUrl(urls.get(attachment.getFilePath())); } - StringBuilder strBuilder = new StringBuilder(); - for (Map.Entry entry : urls.entrySet()) { - if (strBuilder.length() > 0) strBuilder.append("\n"); - strBuilder.append(entry.getValue()); - } - - messageItem.setText(strBuilder.toString()); + messageItem.setText(""); messageItem.setSent(false); messageItem.setInProgress(false); messageItem.setError(false); @@ -458,6 +461,12 @@ public Collection getActiveChats() { return Collections.unmodifiableCollection(collection); } + public AbstractChat getOrCreateChat(AccountJid account, UserJid user, MessageItem lastMessage) { + AbstractChat chat = getOrCreateChat(account, user); + chat.setLastMessage(lastMessage); + return chat; + } + /** * Returns existed chat or create new one. * @@ -536,7 +545,7 @@ public void removeVisibleChat() { * @param chat * @return Whether specified chat is currently visible. */ - boolean isVisibleChat(AbstractChat chat) { + public boolean isVisibleChat(AbstractChat chat) { return visibleChat == chat; } @@ -779,13 +788,21 @@ public void processCarbonsMessage(AccountJid account, final Message message, Car RealmList forwardIds = finalChat.parseForwardedMessage(true, message, uid); String originalStanza = message.toXML().toString(); String originalFrom = message.getFrom().toString(); + + // forward comment (to support previous forwarded xep) String forwardComment = ForwardManager.parseForwardComment(message); if (forwardComment != null) text = forwardComment; + // modify body with references + Pair bodies = ReferencesManager.modifyBodyWithReferences(message, text); + text = bodies.first; + String markupText = bodies.second; + MessageItem newMessageItem = finalChat.createNewMessageItem(text); - newMessageItem.setStanzaId(message.getStanzaId()); + newMessageItem.setStanzaId(AbstractChat.getStanzaId(message)); newMessageItem.setSent(true); newMessageItem.setForwarded(true); + if (markupText != null) newMessageItem.setMarkupText(markupText); // forwarding if (forwardIds != null) newMessageItem.setForwardedIds(forwardIds); diff --git a/xabber/src/main/java/com/xabber/android/data/message/ReceiptManager.java b/xabber/src/main/java/com/xabber/android/data/message/ReceiptManager.java index fe2601f2a9..33d7d2fb48 100644 --- a/xabber/src/main/java/com/xabber/android/data/message/ReceiptManager.java +++ b/xabber/src/main/java/com/xabber/android/data/message/ReceiptManager.java @@ -100,7 +100,7 @@ public void run() { // TODO setDefaultAutoReceiptMode should be used for (ExtensionElement packetExtension : message.getExtensions()) { if (packetExtension instanceof DeliveryReceiptRequest) { - String id = message.getStanzaId(); + String id = AbstractChat.getStanzaId(message); if (id == null) { continue; } @@ -124,7 +124,7 @@ private void markAsError(final AccountJid account, final Message message) { realm.beginTransaction(); MessageItem first = realm.where(MessageItem.class) .equalTo(MessageItem.Fields.ACCOUNT, account.toString()) - .equalTo(MessageItem.Fields.STANZA_ID, message.getStanzaId()).findFirst(); + .equalTo(MessageItem.Fields.STANZA_ID, AbstractChat.getStanzaId(message)).findFirst(); if (first != null) { first.setError(true); XMPPError error = message.getError(); diff --git a/xabber/src/main/java/com/xabber/android/data/message/RegularChat.java b/xabber/src/main/java/com/xabber/android/data/message/RegularChat.java index 05b6d9bcb4..bf21d12ac7 100644 --- a/xabber/src/main/java/com/xabber/android/data/message/RegularChat.java +++ b/xabber/src/main/java/com/xabber/android/data/message/RegularChat.java @@ -17,6 +17,7 @@ import android.content.Intent; import android.support.annotation.NonNull; import android.text.TextUtils; +import android.util.Pair; import com.xabber.android.data.NetworkException; import com.xabber.android.data.SettingsManager; @@ -25,12 +26,12 @@ import com.xabber.android.data.database.messagerealm.MessageItem; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.entity.UserJid; -import com.xabber.android.data.extension.chat_markers.ChatMarkerManager; import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager; import com.xabber.android.data.extension.muc.MUCManager; import com.xabber.android.data.extension.otr.OTRManager; import com.xabber.android.data.extension.otr.OTRUnencryptedException; import com.xabber.android.data.extension.otr.SecurityLevel; +import com.xabber.android.data.extension.references.ReferencesManager; import com.xabber.android.data.log.LogManager; import com.xabber.android.data.xaccount.XMPPAuthManager; @@ -143,7 +144,7 @@ protected String prepareText(String text) { @Override protected MessageItem createNewMessageItem(String text) { - return createMessageItem(null, text, null, null, false, + return createMessageItem(null, text, null, null, null, false, false, false, false, UUID.randomUUID().toString(), null, null, null, account.getFullJid().toString(), null, false); @@ -178,6 +179,11 @@ protected boolean onPacket(UserJid bareAddress, Stanza packet, boolean isCarbons if (text == null) return true; + DelayInformation delayInformation = message.getExtension(DelayInformation.ELEMENT, DelayInformation.NAMESPACE); + if (delayInformation != null && "Offline Storage".equals(delayInformation.getReason())) { + return true; + } + // Xabber service message received if (message.getType() == Type.headline) { if (XMPPAuthManager.getInstance().isXabberServiceMessage(message.getStanzaId())) @@ -214,6 +220,8 @@ protected boolean onPacket(UserJid bareAddress, Stanza packet, boolean isCarbons RealmList forwardIds = parseForwardedMessage(true, packet, uid); String originalStanza = packet.toXML().toString(); String originalFrom = packet.getFrom().toString(); + + // forward comment (to support previous forwarded xep) String forwardComment = ForwardManager.parseForwardComment(packet); if (forwardComment != null) text = forwardComment; @@ -221,19 +229,24 @@ protected boolean onPacket(UserJid bareAddress, Stanza packet, boolean isCarbons if ((text == null || text.trim().equals("")) && (forwardIds == null || forwardIds.isEmpty())) return true; + // modify body with references + Pair bodies = ReferencesManager.modifyBodyWithReferences(message, text); + text = bodies.first; + String markupText = bodies.second; + // create message with file-attachments if (attachments.size() > 0) - createAndSaveFileMessage(true, uid, resource, text, null, getDelayStamp(message), - true, true, encrypted, + createAndSaveFileMessage(true, uid, resource, text, markupText, null, + null, getDelayStamp(message), true, true, encrypted, isOfflineMessage(account.getFullJid().getDomain(), packet), - packet.getStanzaId(), attachments, originalStanza, null, + getStanzaId(message), attachments, originalStanza, null, originalFrom, false, false); // create message without attachments - else createAndSaveNewMessage(true, uid, resource, text, null, getDelayStamp(message), - true, true, encrypted, + else createAndSaveNewMessage(true, uid, resource, text, markupText, null, + null, getDelayStamp(message), true, true, encrypted, isOfflineMessage(account.getFullJid().getDomain(), packet), - packet.getStanzaId(), originalStanza, null, + getStanzaId(message), originalStanza, null, originalFrom, forwardIds,false, false); EventBus.getDefault().post(new NewIncomingMessageEvent(account, user)); @@ -242,7 +255,7 @@ else createAndSaveNewMessage(true, uid, resource, text, null, getDelayStamp(mess } @Override - protected String parseInnerMessage(boolean ui, Message message, String parentMessageId) { + protected String parseInnerMessage(boolean ui, Message message, Date timestamp, String parentMessageId) { if (message.getType() == Message.Type.error) return null; MUCUser mucUser = MUCUser.from(message); @@ -264,19 +277,27 @@ protected String parseInnerMessage(boolean ui, Message message, String parentMes String originalFrom = ""; if (fromJid != null) originalFrom = fromJid.toString(); boolean fromMuc = message.getType().equals(Type.groupchat); + + // forward comment (to support previous forwarded xep) String forwardComment = ForwardManager.parseForwardComment(message); - if (forwardComment != null && !forwardComment.isEmpty()) - text = forwardComment; + if (forwardComment != null && !forwardComment.isEmpty()) text = forwardComment; + + // modify body with references + Pair bodies = ReferencesManager.modifyBodyWithReferences(message, text); + text = bodies.first; + String markupText = bodies.second; // create message with file-attachments if (attachments.size() > 0) - createAndSaveFileMessage(ui, uid, resource, text, null, null, true, - false, encrypted, false, message.getStanzaId(), attachments, + createAndSaveFileMessage(ui, uid, resource, text, markupText, null, + timestamp, getDelayStamp(message), true, false, encrypted, + false, getStanzaId(message), attachments, originalStanza, parentMessageId, originalFrom, fromMuc, true); // create message without attachments - else createAndSaveNewMessage(ui, uid, resource, text, null, null, true, - false, encrypted, false, message.getStanzaId(), originalStanza, + else createAndSaveNewMessage(ui, uid, resource, text, markupText, null, + timestamp, getDelayStamp(message), true, false, encrypted, + false, getStanzaId(message), originalStanza, parentMessageId, originalFrom, forwardIds, fromMuc, true); return uid; @@ -292,16 +313,6 @@ public static boolean isOfflineMessage(Domainpart server, Stanza stanza) { && TextUtils.equals(delayInformation.getFrom(), server); } - public static Date getDelayStamp(Message message) { - DelayInformation delayInformation = DelayInformation.from(message); - if (delayInformation != null) { - return delayInformation.getStamp(); - } else { - return null; - } - } - - @Override protected void onComplete() { super.onComplete(); diff --git a/xabber/src/main/java/com/xabber/android/data/message/chat/ChatManager.java b/xabber/src/main/java/com/xabber/android/data/message/chat/ChatManager.java index c7111125ca..091cc2507f 100644 --- a/xabber/src/main/java/com/xabber/android/data/message/chat/ChatManager.java +++ b/xabber/src/main/java/com/xabber/android/data/message/chat/ChatManager.java @@ -511,6 +511,7 @@ public void execute(Realm realm) { chatRealm.setLastPosition(chat.getLastPosition()); chatRealm.setArchived(chat.isArchived()); + chatRealm.setHistoryRequestedAtStart(chat.isHistoryRequestedAtStart()); NotificationStateRealm notificationStateRealm = chatRealm.getNotificationState(); if (notificationStateRealm == null) @@ -557,7 +558,8 @@ public ChatData loadChatDataFromRealm(AbstractChat chat) { realmChat.getUserJid(), realmChat.isArchived(), notificationState, - realmChat.getLastPosition()); + realmChat.getLastPosition(), + realmChat.isHistoryRequestedAtStart()); } realm.close(); diff --git a/xabber/src/main/java/com/xabber/android/data/notification/Action.java b/xabber/src/main/java/com/xabber/android/data/notification/Action.java new file mode 100644 index 0000000000..89eb6c1aae --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/notification/Action.java @@ -0,0 +1,39 @@ +package com.xabber.android.data.notification; + +public class Action { + private int notificationID; + private CharSequence replyText; + private ActionType actionType; + + public Action(int notificationID, CharSequence replyText, ActionType actionType) { + this.notificationID = notificationID; + this.replyText = replyText; + this.actionType = actionType; + } + + public Action(int notificationID, ActionType actionType) { + this.notificationID = notificationID; + this.actionType = actionType; + } + + public int getNotificationID() { + return notificationID; + } + + public CharSequence getReplyText() { + return replyText; + } + + public ActionType getActionType() { + return actionType; + } + + public enum ActionType { + reply, + read, + snooze, + cancel + } +} + + diff --git a/xabber/src/main/java/com/xabber/android/data/notification/DelayedNotificationActionManager.java b/xabber/src/main/java/com/xabber/android/data/notification/DelayedNotificationActionManager.java new file mode 100644 index 0000000000..7b015dbac4 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/notification/DelayedNotificationActionManager.java @@ -0,0 +1,52 @@ +package com.xabber.android.data.notification; + +import com.xabber.android.data.Application; +import com.xabber.android.data.connection.ConnectionItem; +import com.xabber.android.data.connection.listeners.OnConnectedListener; +import java.util.ArrayList; +import java.util.List; + +public class DelayedNotificationActionManager implements OnConnectedListener { + + private static DelayedNotificationActionManager instance; + private List delayedActions = new ArrayList<>(); + + public static DelayedNotificationActionManager getInstance() { + if (instance == null) + instance = new DelayedNotificationActionManager(); + return instance; + } + + @Override + public void onConnected(ConnectionItem connection) { + Application.getInstance().runInBackground(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + Application.getInstance().runOnUiThread(new Runnable() { + @Override + public void run() { + onLoaded(); + } + }); + } + }); + } + + private void onLoaded() { + for (FullAction action : delayedActions) { + MessageNotificationManager.getInstance().performAction(action); + } + delayedActions.clear(); + } + + public void addAction(FullAction action) { + delayedActions.add(action); + } + +} diff --git a/xabber/src/main/java/com/xabber/android/data/notification/FullAction.java b/xabber/src/main/java/com/xabber/android/data/notification/FullAction.java new file mode 100644 index 0000000000..fd81fca17a --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/notification/FullAction.java @@ -0,0 +1,24 @@ +package com.xabber.android.data.notification; + +import com.xabber.android.data.entity.AccountJid; +import com.xabber.android.data.entity.UserJid; + +public class FullAction extends Action { + + private AccountJid accountJid; + private UserJid userJid; + + public FullAction(Action action, AccountJid accountJid, UserJid userJid) { + super(action.getNotificationID(), action.getReplyText(), action.getActionType()); + this.accountJid = accountJid; + this.userJid = userJid; + } + + public AccountJid getAccountJid() { + return accountJid; + } + + public UserJid getUserJid() { + return userJid; + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationCreator.java b/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationCreator.java index 5a2ebd9c17..80d664b7c9 100644 --- a/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationCreator.java +++ b/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationCreator.java @@ -12,6 +12,7 @@ import android.support.v4.app.NotificationCompat; import android.support.v4.app.Person; import android.support.v4.app.RemoteInput; +import android.support.v4.graphics.drawable.IconCompat; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; @@ -78,7 +79,7 @@ public void createNotification(MessageNotificationManager.Chat chat, boolean ale boolean showText = isNeedShowTextInNotification(chat); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - builder.addAction(createReplyAction(chat.getNotificationId())) + builder.addAction(createReplyAction(chat.getNotificationId(), chat.getAccountJid())) .setStyle(createMessageStyle(chat, showText)); } else { builder.setContentTitle(createTitleSingleChat(chat.getMessages().size(), chat.getChatTitle())) @@ -90,8 +91,8 @@ public void createNotification(MessageNotificationManager.Chat chat, boolean ale if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O && alert && !inGracePeriod(chat)) addEffects(builder, chat.getLastMessage().getMessageText().toString(), chat, context); - builder.addAction(createMarkAsReadAction(chat.getNotificationId())) - .addAction(createMuteAction(chat.getNotificationId())); + builder.addAction(createMarkAsReadAction(chat.getNotificationId(), chat.getAccountJid())) + .addAction(createMuteAction(chat.getNotificationId(), chat.getAccountJid())); sendNotification(builder, chat.getNotificationId()); } @@ -152,6 +153,7 @@ private String getChannelID(MessageNotificationManager.Chat chat) { } private void sendNotification(NotificationCompat.Builder builder, int notificationId) { + MessageNotificationManager.getInstance().setLastNotificationTime(); try { notificationManager.notify(notificationId, builder.build()); } catch (SecurityException e) { @@ -164,6 +166,7 @@ private void sendNotification(NotificationCompat.Builder builder, int notificati /** UTILS */ private static boolean inGracePeriod(MessageNotificationManager.Chat chat) { + if (!MessageNotificationManager.getInstance().isTimeToNewFullNotification()) return true; if (chat == null) return false; AccountItem accountItem = AccountManager.getInstance().getAccount(chat.getAccountJid()); if (accountItem != null) return accountItem.inGracePeriod(); @@ -182,19 +185,22 @@ private CharSequence createTitleSingleChat(int messageCount, CharSequence chatTi } private NotificationCompat.Style createMessageStyle(MessageNotificationManager.Chat chat, boolean showText) { - NotificationCompat.Style messageStyle = new NotificationCompat.MessagingStyle( + NotificationCompat.MessagingStyle messageStyle = new NotificationCompat.MessagingStyle( new Person.Builder().setName(context.getString(R.string.sender_is_you)).build()); for (MessageNotificationManager.Message message : chat.getMessages()) { Person person = null; - if (message.getAuthor() != null && message.getAuthor().length() > 0) - person = new Person.Builder().setName(message.getAuthor()).build(); - ((NotificationCompat.MessagingStyle) messageStyle).addMessage( - new NotificationCompat.MessagingStyle.Message( + if (message.getAuthor() != null && message.getAuthor().length() > 0) { + person = new Person.Builder() + .setName(message.getAuthor()) + .setIcon(IconCompat.createWithBitmap(getLargeIcon(chat))) + .build(); + } + messageStyle.addMessage(new NotificationCompat.MessagingStyle.Message( showText ? message.getMessageText() : messageHidden, message.getTimestamp(), person)); } - ((NotificationCompat.MessagingStyle) messageStyle).setConversationTitle(chat.getChatTitle()); - ((NotificationCompat.MessagingStyle) messageStyle).setGroupConversation(chat.isGroupChat()); + messageStyle.setConversationTitle(chat.getChatTitle()); + messageStyle.setGroupConversation(chat.isGroupChat()); return messageStyle; } @@ -363,26 +369,26 @@ private boolean isAppInForeground(Context context) { /** ACTIONS */ - private NotificationCompat.Action createReplyAction(int notificationId) { + private NotificationCompat.Action createReplyAction(int notificationId, AccountJid accountJid) { RemoteInput remoteInput = new RemoteInput.Builder(NotificationReceiver.KEY_REPLY_TEXT) .setLabel(context.getString(R.string.chat_input_hint)) .build(); return new NotificationCompat.Action.Builder(R.drawable.ic_message_forwarded_14dp, - context.getString(R.string.action_reply), NotificationReceiver.createReplyIntent(context, notificationId)) + context.getString(R.string.action_reply), NotificationReceiver.createReplyIntent(context, notificationId, accountJid)) .addRemoteInput(remoteInput) .build(); } - private NotificationCompat.Action createMarkAsReadAction(int notificationId) { + private NotificationCompat.Action createMarkAsReadAction(int notificationId, AccountJid accountJid) { return new NotificationCompat.Action.Builder(R.drawable.ic_mark_as_read, - context.getString(R.string.action_mark_as_read), NotificationReceiver.createMarkAsReadIntent(context, notificationId)) + context.getString(R.string.action_mark_as_read), NotificationReceiver.createMarkAsReadIntent(context, notificationId, accountJid)) .build(); } - private NotificationCompat.Action createMuteAction(int notificationId) { + private NotificationCompat.Action createMuteAction(int notificationId, AccountJid accountJid) { return new NotificationCompat.Action.Builder(R.drawable.ic_snooze, - context.getString(R.string.action_snooze), NotificationReceiver.createMuteIntent(context, notificationId)) + context.getString(R.string.action_snooze), NotificationReceiver.createMuteIntent(context, notificationId, accountJid)) .build(); } diff --git a/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationManager.java b/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationManager.java index ed6dedc77c..f1baac8cf6 100644 --- a/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationManager.java +++ b/xabber/src/main/java/com/xabber/android/data/notification/MessageNotificationManager.java @@ -25,6 +25,7 @@ import com.xabber.android.data.roster.RosterManager; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.UUID; @@ -42,6 +43,8 @@ public class MessageNotificationManager implements OnLoadListener { private static MessageNotificationManager instance; private List chats = new ArrayList<>(); private Message lastMessage = null; + private HashMap delayedActions = new HashMap<>(); + private long lastNotificationTime = 0; private MessageNotificationManager() { context = Application.getInstance(); @@ -69,60 +72,40 @@ public static MessageNotificationManager getInstance() { return instance; } - /** LISTENER */ - - public void onNotificationReplied(int notificationId, final CharSequence replyText) { - final Chat chat = getChat(notificationId); - if (chat != null) { - // send message - MessageManager.getInstance().sendMessage( - chat.getAccountJid(), chat.getUserJid(), replyText.toString()); - - // update notification - addMessage(chat, "", replyText, false); - saveNotifChatToRealm(chat); - } + public boolean isTimeToNewFullNotification() { + return System.currentTimeMillis() > (lastNotificationTime + 1000); } - public void onNotificationCanceled(int notificationId) { - if (notificationId == MESSAGE_BUNDLE_NOTIFICATION_ID) - removeAllMessageNotifications(); - else removeChat(notificationId); + public void setLastNotificationTime() { + this.lastNotificationTime = System.currentTimeMillis(); } - public void onNotificationMuted(int notificationId) { - Chat chatNotif = getChat(notificationId); - if (chatNotif != null) { - AbstractChat chat = MessageManager.getInstance().getChat( - chatNotif.getAccountJid(), chatNotif.getUserJid()); + /** LISTENER */ + + public void onNotificationAction(Action action) { + if (action.getActionType() != Action.ActionType.cancel) { + Chat chat = getChat(action.getNotificationID()); if (chat != null) { - chat.setNotificationState(new NotificationState(NotificationState.NotificationMode.snooze2h, - (int) (System.currentTimeMillis() / 1000L)), true); - callUiUpdate(); + performAction(new FullAction(action, chat.getAccountJid(), chat.getUserJid())); + + // update notification + if (action.getActionType() == Action.ActionType.reply) { + addMessage(chat, "", action.getReplyText(), false); + saveNotifChatToRealm(chat); + } } } // cancel notification - notificationManager.cancel(notificationId); - onNotificationCanceled(notificationId); - } - - public void onNotificationMarkedAsRead(int notificationId) { - // mark chat as read - Chat chatNotif = getChat(notificationId); - if (chatNotif != null) { - AbstractChat chat = MessageManager.getInstance().getChat( - chatNotif.getAccountJid(), chatNotif.getUserJid()); - if (chat != null) { - AccountManager.getInstance().stopGracePeriod(chat.getAccount()); - chat.markAsReadAll(true); - callUiUpdate(); - } + if (action.getActionType() != Action.ActionType.reply) { + notificationManager.cancel(action.getNotificationID()); + onNotificationCanceled(action.getNotificationID()); } + } - // cancel notification - notificationManager.cancel(notificationId); - onNotificationCanceled(notificationId); + public void onDelayedNotificationAction(Action action) { + notificationManager.cancel(action.getNotificationID()); + delayedActions.put(action.getNotificationID(), action); } @Override @@ -214,13 +197,57 @@ public void rebuildAllNotifications() { /** PRIVATE METHODS */ + private void onNotificationCanceled(int notificationId) { + if (notificationId == MESSAGE_BUNDLE_NOTIFICATION_ID) + removeAllMessageNotifications(); + else removeChat(notificationId); + } + + public void performAction(FullAction action) { + AccountJid accountJid = action.getAccountJid(); + UserJid userJid = action.getUserJid(); + + switch (action.getActionType()) { + case read: + AbstractChat chat = MessageManager.getInstance().getChat(accountJid, userJid); + if (chat != null) { + AccountManager.getInstance().stopGracePeriod(chat.getAccount()); + chat.markAsReadAll(true); + callUiUpdate(); + } + break; + case snooze: + AbstractChat chat1 = MessageManager.getInstance().getChat(accountJid, userJid); + if (chat1 != null) { + chat1.setNotificationState(new NotificationState(NotificationState.NotificationMode.snooze2h, + (int) (System.currentTimeMillis() / 1000L)), true); + callUiUpdate(); + } + break; + case reply: + MessageManager.getInstance().sendMessage(accountJid, userJid, action.getReplyText().toString()); + } + } + private void onLoaded(List loadedChats) { - this.chats.addAll(loadedChats); - if (loadedChats != null && loadedChats.size() > 0) { - List messages = loadedChats.get(loadedChats.size() - 1).getMessages(); + for (Chat chat : loadedChats) { + if (delayedActions.containsKey(chat.notificationId)) { + Action action = delayedActions.get(chat.notificationId); + if (action != null) { + notificationManager.cancel(action.getNotificationID()); + DelayedNotificationActionManager.getInstance().addAction( + new FullAction(action, chat.getAccountJid(), chat.getUserJid())); + removeNotifChatFromRealm(chat.accountJid, chat.userJid); + } + } else chats.add(chat); + } + delayedActions.clear(); + + if (chats != null && chats.size() > 0) { + List messages = chats.get(chats.size() - 1).getMessages(); if (messages != null && messages.size() > 0) { lastMessage = messages.get(messages.size() - 1); - rebuildAllNotifications(); + //rebuildAllNotifications(); } } } @@ -299,7 +326,7 @@ private void callUiUpdate() { } private int getNextChatNotificationId() { - return 100 + chats.size() + 1; + return (int) System.currentTimeMillis(); } private String getNotificationText(MessageItem message) { diff --git a/xabber/src/main/java/com/xabber/android/data/notification/NotificationManager.java b/xabber/src/main/java/com/xabber/android/data/notification/NotificationManager.java index da804d1cb5..1a17a233e5 100644 --- a/xabber/src/main/java/com/xabber/android/data/notification/NotificationManager.java +++ b/xabber/src/main/java/com/xabber/android/data/notification/NotificationManager.java @@ -40,6 +40,7 @@ import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.entity.UserJid; import com.xabber.android.data.log.LogManager; +import com.xabber.android.data.push.SyncManager; import com.xabber.android.service.XabberService; import com.xabber.android.ui.activity.ClearNotificationsActivity; import com.xabber.android.ui.activity.ContactListActivity; @@ -269,14 +270,12 @@ public void startVibration() { } private void updatePersistentNotification() { - if (!SettingsManager.eventsPersistent()) { - return; - } - if (XabberService.getInstance() == null) return; // we do not want to show persistent notification if there are no enabled accounts XabberService.getInstance().changeForeground(); + if (!XabberService.getInstance().needForeground()) return; + Collection accountList = AccountManager.getInstance().getEnabledAccounts(); if (accountList.isEmpty()) { return; @@ -321,15 +320,22 @@ private void updatePersistentNotification() { persistentIntent = ContactListActivity.createPersistentIntent(application); - if (connected > 0) { - persistentNotificationBuilder.setColor(persistentNotificationColor); - persistentNotificationBuilder.setSmallIcon(R.drawable.ic_stat_online); - } else { + if (SyncManager.getInstance().isSyncMode()) { persistentNotificationBuilder.setColor(NotificationCompat.COLOR_DEFAULT); - persistentNotificationBuilder.setSmallIcon(R.drawable.ic_stat_offline); + persistentNotificationBuilder.setSmallIcon(R.drawable.ic_sync); + persistentNotificationBuilder.setContentText(application.getString(R.string.connection_state_sync)); + } else { + if (connected > 0) { + persistentNotificationBuilder.setColor(persistentNotificationColor); + persistentNotificationBuilder.setSmallIcon(R.drawable.ic_stat_online); + } else { + persistentNotificationBuilder.setColor(NotificationCompat.COLOR_DEFAULT); + persistentNotificationBuilder.setSmallIcon(R.drawable.ic_stat_offline); + } + + persistentNotificationBuilder.setContentText(getConnectionState(waiting, connecting, connected, accountList.size())); } - persistentNotificationBuilder.setContentText(getConnectionState(waiting, connecting, connected, accountList.size())); persistentNotificationBuilder.setContentIntent(PendingIntent.getActivity(application, 0, persistentIntent, PendingIntent.FLAG_UPDATE_CURRENT)); @@ -456,6 +462,7 @@ public Notification getPersistentNotification() { @Override public void onClose() { - notificationManager.cancelAll(); + //notificationManager.cancelAll(); + notificationManager.cancel(PERSISTENT_NOTIFICATION_ID); } } diff --git a/xabber/src/main/java/com/xabber/android/data/push/PushManager.java b/xabber/src/main/java/com/xabber/android/data/push/PushManager.java new file mode 100644 index 0000000000..253e2a1326 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/push/PushManager.java @@ -0,0 +1,281 @@ +package com.xabber.android.data.push; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.provider.Settings; +import android.util.Log; + +import com.google.firebase.iid.FirebaseInstanceId; +import com.xabber.android.BuildConfig; +import com.xabber.android.data.Application; +import com.xabber.android.data.SettingsManager; +import com.xabber.android.data.account.AccountItem; +import com.xabber.android.data.account.AccountManager; +import com.xabber.android.data.connection.ConnectionItem; +import com.xabber.android.data.connection.listeners.OnConnectedListener; +import com.xabber.android.data.connection.listeners.OnPacketListener; +import com.xabber.android.data.database.RealmManager; +import com.xabber.android.data.database.realm.PushLogRecord; +import com.xabber.android.data.entity.AccountJid; +import com.xabber.android.data.entity.UserJid; +import com.xabber.android.data.http.PushApiClient; +import com.xabber.android.data.log.LogManager; +import com.xabber.android.utils.Utils; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smack.tcp.XMPPTCPConnection; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.push_notifications.element.EnablePushNotificationsIQ; +import org.jivesoftware.smackx.push_notifications.element.PushNotificationsElements; +import org.jxmpp.jid.EntityBareJid; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +import io.realm.Realm; +import io.realm.RealmResults; +import io.realm.Sort; +import okhttp3.ResponseBody; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.schedulers.Schedulers; +import rx.subscriptions.CompositeSubscription; + +public class PushManager implements OnConnectedListener, OnPacketListener { + + private static final String LOG_TAG = PushManager.class.getSimpleName(); + + private static PushManager instance; + + public static PushManager getInstance() { + if (instance == null) + instance = new PushManager(); + return instance; + } + + private CompositeSubscription compositeSubscription = new CompositeSubscription(); + private HashMap waitingIQs = new HashMap<>(); + + /** Listeners */ + + @Override + public void onConnected(final ConnectionItem connection) { + Application.getInstance().runInBackground(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + AccountJid accountJid = connection.getAccount(); + AccountItem accountItem = AccountManager.getInstance().getAccount(accountJid); + enablePushNotificationsIfNeed(accountItem); + } + }); + } + + public void onEndpointRegistered(String jid, String pushServiceJid, String node) { + LogManager.d(LOG_TAG, "Received endpoint-registered push. Send push enable iq."); + AccountJid accountJid = null; + Collection accounts = AccountManager.getInstance().getEnabledAccounts(); + for (AccountJid account : accounts) { + if ((account.getFullJid().asBareJid() + getAndroidId()).equals(jid)) { + accountJid = account; + break; + } + } + + if (accountJid != null) { + AccountItem account = AccountManager.getInstance().getAccount(accountJid); + if (account != null) { + + // save node to account + AccountManager.getInstance().setPushNode(account, node, pushServiceJid); + + // update push nodes + updateEnabledPushNodes(); + + // enable push on XMPP-server + sendEnablePushIQ(account, pushServiceJid, node); + } + } + } + + public void onNewMessagePush(Context context, String node) { + String message; + if (!Application.getInstance().isServiceStarted() + && SettingsManager.getEnabledPushNodes().contains(node)) { + Utils.startXabberServiceCompatWithSyncMode(context, node); + message = "Starting service"; + } else if (SyncManager.getInstance().isSyncMode()) { + message = "Service also started. Add account to allowed accounts"; + SyncManager.getInstance().addAllowedAccount(node); + } else message = "Service also started. Not a sync mode - account maybe connected"; + + LogManager.d(LOG_TAG, "Received message push. " + message); + if (BuildConfig.FLAVOR.equals("dev")) addToPushLog(message); + } + + @Override + public void onStanza(ConnectionItem connection, Stanza packet) { + if (packet instanceof IQ && ((IQ) packet).getType() != IQ.Type.error) { + if (waitingIQs.containsKey(packet.getStanzaId())) { + + AccountJid account = connection.getAccount(); + AccountItem accountItem = AccountManager.getInstance().getAccount(account); + Boolean enable = waitingIQs.get(packet.getStanzaId()); + if (accountItem != null && enable != null) { + AccountManager.getInstance().setPushWasEnabled(accountItem, enable); + } + waitingIQs.remove(packet.getStanzaId()); + } + } + } + + /** Api */ + + public void enablePushNotificationsIfNeed(AccountItem accountItem) { + if (accountItem != null && accountItem.isPushEnabled() && accountItem.getConnection().isConnected()) { + if (isSupport(accountItem.getConnection())) { + registerEndpoint(accountItem.getAccount()); + } else AccountManager.getInstance().setPushWasEnabled(accountItem, false); + } + } + + public void disablePushNotification(AccountItem accountItem, boolean needConfirm) { + if (accountItem != null) { + deleteEndpoint(accountItem); + AccountManager.getInstance().setPushWasEnabled(accountItem, false); + } + } + + public void updateEnabledPushNodes() { + StringBuilder stringBuilder = new StringBuilder(); + for (AccountItem accountItem : AccountManager.getInstance().getAllAccountItems()) { + String node = accountItem.getPushNode(); + if (accountItem.isEnabled() && accountItem.isPushEnabled() + && accountItem.isPushWasEnabled() && node != null && !node.isEmpty()) { + stringBuilder.append(node); + stringBuilder.append(" "); + } + } + SettingsManager.setEnabledPushNodes(stringBuilder.toString()); + } + + public boolean isSupport(XMPPTCPConnection connection) { + try { + EntityBareJid jid = connection.getUser().asEntityBareJid(); + return ServiceDiscoveryManager.getInstanceFor(connection) + .supportsFeatures(jid, Collections.singletonList(PushNotificationsElements.NAMESPACE)); + + } catch (SmackException.NoResponseException | XMPPException.XMPPErrorException + | SmackException.NotConnectedException | InterruptedException e) { + return false; + } + } + + /** Log */ + + private void addToPushLog(String message) { + Realm realm = RealmManager.getInstance().getNewBackgroundRealm(); + PushLogRecord pushLogRecord = new PushLogRecord(System.currentTimeMillis(), message); + realm.beginTransaction(); + realm.copyToRealm(pushLogRecord); + realm.commitTransaction(); + realm.close(); + } + + public static List getPushLogs() { + Realm realm = RealmManager.getInstance().getRealmUiThread(); + List logs = new ArrayList<>(); + RealmResults records = realm.where(PushLogRecord.class) + .findAllSorted(PushLogRecord.Fields.TIME, Sort.DESCENDING); + for (PushLogRecord record : records) { + String time = new SimpleDateFormat("yyyy.MM.dd - HH:mm:ss", + Locale.getDefault()).format(new Date(record.getTime())); + logs.add(time + ": " + record.getMessage()); + } + return logs; + } + + public static void clearPushLog() { + Realm realm = RealmManager.getInstance().getRealmUiThread(); + RealmResults records = realm.where(PushLogRecord.class).findAll(); + realm.beginTransaction(); + records.deleteAllFromRealm(); + realm.commitTransaction(); + } + + /** Private */ + + private void registerEndpoint(AccountJid accountJid) { + compositeSubscription.add( + PushApiClient.registerEndpoint( + FirebaseInstanceId.getInstance().getToken(), + accountJid.getFullJid().asBareJid().toString() + getAndroidId()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(ResponseBody responseBody) { + Log.d(LOG_TAG, "Endpoint successfully registered"); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + Log.d(LOG_TAG, "Endpoint register failed: " + throwable.toString()); + } + })); + } + + private void deleteEndpoint(final AccountItem accountItem) { + compositeSubscription.add( + PushApiClient.deleteEndpoint( + FirebaseInstanceId.getInstance().getToken(), accountItem.getAccount().toString()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1() { + @Override + public void call(ResponseBody responseBody) { + Log.d(LOG_TAG, "Endpoint successfully unregistered"); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + Log.d(LOG_TAG, "Endpoint unregister failed: " + throwable.toString()); + } + })); + } + + private void sendEnablePushIQ(final AccountItem accountItem, final String pushServiceJid, final String node) { + String stanzaID = null; + try { + EnablePushNotificationsIQ enableIQ = new EnablePushNotificationsIQ( + UserJid.from(pushServiceJid).getJid(), node, null); + stanzaID = enableIQ.getStanzaId(); + waitingIQs.put(stanzaID, true); + accountItem.getConnection().sendStanza(enableIQ); + } catch (SmackException.NotConnectedException | InterruptedException | UserJid.UserJidCreateException e) { + Log.d(LOG_TAG, "Push notification enabling failed: " + e.toString()); + waitingIQs.remove(stanzaID); + AccountManager.getInstance().setPushWasEnabled(accountItem, false); + } + } + + @SuppressLint("HardwareIds") + private static String getAndroidId() { + return "/" + Settings.Secure.getString(Application.getInstance().getContentResolver(), + Settings.Secure.ANDROID_ID); + } + +} diff --git a/xabber/src/main/java/com/xabber/android/data/push/SyncManager.java b/xabber/src/main/java/com/xabber/android/data/push/SyncManager.java new file mode 100644 index 0000000000..1188acec62 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/push/SyncManager.java @@ -0,0 +1,137 @@ +package com.xabber.android.data.push; + +import android.content.Context; +import android.content.Intent; +import android.os.Parcelable; + +import com.xabber.android.data.OnTimerListener; +import com.xabber.android.data.account.AccountItem; +import com.xabber.android.data.entity.AccountJid; +import com.xabber.android.service.XabberService; + +import java.util.ArrayList; +import java.util.List; + +public class SyncManager implements OnTimerListener { + + private static final long SYNC_TIME = 60000; + public static final String SYNC_MODE = "SYNC_MODE"; + public static final String PUSH_NODE = "PUSH_NODE"; + public static final String ACCOUNT_JID = "ACCOUNT_JID"; + + private static SyncManager instance; + private boolean syncMode; + private boolean syncPeriod; + private boolean syncActionDone; + private boolean syncNotifActionDone; + + private long timestamp; + private List pushNodes = new ArrayList<>(); + private List accountJids = new ArrayList<>(); + + public static SyncManager getInstance() { + if (instance == null) + instance = new SyncManager(); + return instance; + } + + @Override + public void onTimer() { + if (syncPeriod && syncMode && isTimeToStopSyncPeriod()) { + stopSyncPeriod(); + XabberService.getInstance().changeForeground(); + } + } + + public void onMessageSaved() { + this.syncActionDone = true; + } + + public void onServiceStarted(Intent intent) { + if (intent != null && intent.getBooleanExtra(SYNC_MODE, false)) { + if (intent.hasExtra(PUSH_NODE)) startSyncMode(intent.getStringExtra(PUSH_NODE)); + else if (intent.hasExtra(ACCOUNT_JID)) startSyncMode((AccountJid) intent.getParcelableExtra(ACCOUNT_JID)); + } + } + + public void onActivityResume() { + stopSyncMode(); + } + + public boolean isAccountNeedConnection(AccountItem accountItem) { + if (!syncMode || (pushNodes.isEmpty() && accountJids.isEmpty())) return true; + return pushNodes.contains(accountItem.getPushNode()) || accountJids.contains(accountItem.getAccount()); + } + + public boolean isSyncPeriod() { + return syncPeriod; + } + + public boolean isSyncMode() { + return syncMode; + } + + public static Intent createXabberServiceIntentWithSyncMode(Context context, String pushNode) { + Intent intent = new Intent(context, XabberService.class); + intent.putExtra(SYNC_MODE, true); + intent.putExtra(PUSH_NODE, pushNode); + return intent; + } + + public static Intent createXabberServiceIntentWithSyncMode(Context context, AccountJid accountJid) { + Intent intent = new Intent(context, XabberService.class); + intent.putExtra(SYNC_MODE, true); + intent.putExtra(ACCOUNT_JID, (Parcelable) accountJid); + return intent; + } + + public boolean isAccountAllowed(AccountJid account) { + if (!syncMode) return true; + return accountJids.contains(account); + } + + public void addAllowedAccount(String node) { + startSyncMode(node); + XabberService.getInstance().changeForeground(); + } + + public void addAllowedAccount(AccountJid account) { + startSyncMode(account); + XabberService.getInstance().changeForeground(); + } + + private void startSyncMode(String pushNode) { + this.syncMode = true; + this.timestamp = System.currentTimeMillis(); + if (this.pushNodes != null && !this.pushNodes.contains(pushNode)) + this.pushNodes.add(pushNode); + this.syncPeriod = true; + this.syncActionDone = false; + this.syncNotifActionDone = true; + } + + private void startSyncMode(AccountJid accountJid) { + this.syncMode = true; + this.timestamp = System.currentTimeMillis(); + if (this.accountJids != null && !this.accountJids.contains(accountJid)) + this.accountJids.add(accountJid); + this.syncPeriod = true; + this.syncActionDone = true; + this.syncNotifActionDone = false; + } + + private void stopSyncMode() { + this.syncMode = false; + this.pushNodes.clear(); + this.accountJids.clear(); + this.syncPeriod = false; + } + + private void stopSyncPeriod() { + this.syncPeriod = false; + } + + private boolean isTimeToStopSyncPeriod() { + return (syncActionDone && syncNotifActionDone) || System.currentTimeMillis() > timestamp + SYNC_TIME; + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/roster/AbstractContact.java b/xabber/src/main/java/com/xabber/android/data/roster/AbstractContact.java index 096d105585..1edacd8c8d 100644 --- a/xabber/src/main/java/com/xabber/android/data/roster/AbstractContact.java +++ b/xabber/src/main/java/com/xabber/android/data/roster/AbstractContact.java @@ -116,13 +116,6 @@ public Collection getGroups() { } public Drawable getAvatar() { - return AvatarManager.getInstance().getUserAvatar(user, getName()); - } - - /** - * @return Cached avatar's drawable for contact list. - */ - public Drawable getAvatarForContactList() { return AvatarManager.getInstance().getUserAvatarForContactList(user, getName()); } diff --git a/xabber/src/main/java/com/xabber/android/data/roster/CrowdfundingContact.java b/xabber/src/main/java/com/xabber/android/data/roster/CrowdfundingContact.java index 1e3d4da8d9..4a39133397 100644 --- a/xabber/src/main/java/com/xabber/android/data/roster/CrowdfundingContact.java +++ b/xabber/src/main/java/com/xabber/android/data/roster/CrowdfundingContact.java @@ -16,7 +16,7 @@ public CrowdfundingContact(CrowdfundingChat chat) { CrowdfundingMessage lastMessage = chat.getLastCrowdMessage(); if (lastMessage != null) { this.lastMessageText = lastMessage.getMessageForCurrentLocale(); - this.lastMessageTime = new Date((long) lastMessage.getReceivedTimestamp() * 1000); + this.lastMessageTime = new Date((long) lastMessage.getTimestamp() * 1000); } this.unreadCount = chat.getUnreadCount(); } diff --git a/xabber/src/main/java/com/xabber/android/data/roster/RosterCacheManager.java b/xabber/src/main/java/com/xabber/android/data/roster/RosterCacheManager.java new file mode 100644 index 0000000000..33fb35e207 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/data/roster/RosterCacheManager.java @@ -0,0 +1,125 @@ +package com.xabber.android.data.roster; + +import com.xabber.android.data.database.MessageDatabaseManager; +import com.xabber.android.data.database.messagerealm.MessageItem; +import com.xabber.android.data.database.realm.ContactGroup; +import com.xabber.android.data.database.realm.ContactRealm; +import com.xabber.android.data.entity.AccountJid; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.realm.Realm; +import io.realm.RealmList; +import io.realm.RealmResults; + +public class RosterCacheManager { + + private static RosterCacheManager instance; + private Map lastActivityCache = new HashMap<>(); + + public static RosterCacheManager getInstance() { + if (instance == null) + instance = new RosterCacheManager(); + return instance; + } + + public static List loadContacts() { + Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread(); + return realm.where(ContactRealm.class).findAll(); + } + + public static void saveContact(AccountJid accountJid, Collection contacts) { + Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread(); + + realm.beginTransaction(); + if (contacts.size() > 1) { + RealmResults results = realm.where(ContactRealm.class) + .equalTo(ContactRealm.Fields.ACCOUNT, + accountJid.getFullJid().asBareJid().toString()).findAll(); + results.deleteAllFromRealm(); + } + + List newContacts = new ArrayList<>(); + for (RosterContact contact : contacts) { + String account = contact.getAccount().getFullJid().asBareJid().toString(); + String user = contact.getUser().getBareJid().toString(); + + ContactRealm contactRealm = realm.where(ContactRealm.class).equalTo(ContactRealm.Fields.ID, + account + "/" + user).findFirst(); + if (contactRealm == null) { + contactRealm = new ContactRealm(account + "/" + user); + } + + RealmList groups = new RealmList<>(); + for (String groupName : contact.getGroupNames()) { + ContactGroup group = realm.copyToRealmOrUpdate(new ContactGroup(groupName)); + if (group.isManaged() && group.isValid()) + groups.add(group); + } + + contactRealm.setGroups(groups); + contactRealm.setAccount(account); + contactRealm.setUser(user); + contactRealm.setName(contact.getName()); + contactRealm.setAccountResource(contact.getAccount().getFullJid().getResourcepart().toString()); + newContacts.add(contactRealm); + } + realm.copyToRealmOrUpdate(newContacts); + realm.commitTransaction(); + } + + public static void removeContact(Collection contacts) { + Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread(); + realm.beginTransaction(); + for (RosterContact contact : contacts) { + String account = contact.getAccount().getFullJid().asBareJid().toString(); + String user = contact.getUser().getBareJid().toString(); + + ContactRealm contactRealm = realm.where(ContactRealm.class).equalTo(ContactRealm.Fields.ID, + account + "/" + user).findFirst(); + if (contactRealm != null) + contactRealm.deleteFromRealm(); + } + realm.commitTransaction(); + } + + public static void removeContacts(AccountJid account) { + String accountJid = account.getFullJid().asBareJid().toString(); + Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread(); + RealmResults results = realm.where(ContactRealm.class) + .equalTo(ContactRealm.Fields.ACCOUNT, accountJid).findAll(); + realm.beginTransaction(); + results.deleteAllFromRealm(); + realm.commitTransaction(); + } + + public static void saveLastMessageToContact(Realm realm, MessageItem messageItem) { + if (messageItem == null) return; + final String account = messageItem.getAccount().getFullJid().asBareJid().toString(); + final String user = messageItem.getUser().getBareJid().toString(); + final String messageID = messageItem.getUniqueId(); + realm.executeTransactionAsync(new Realm.Transaction() { + @Override + public void execute(Realm realm) { + ContactRealm contactRealm = realm.where(ContactRealm.class).equalTo(ContactRealm.Fields.ID, account + "/" + user).findFirst(); + MessageItem message = realm.where(MessageItem.class).equalTo(MessageItem.Fields.UNIQUE_ID, messageID).findFirst(); + if (contactRealm != null && message.isValid() && message.isManaged()) { + contactRealm.setLastMessage(message); + realm.copyToRealmOrUpdate(contactRealm); + } + } + }); + } + + public String getCachedLastActivityString(long lastActivityTime) { + return lastActivityCache.get(lastActivityTime); + } + + public void putLastActivityStringToCache(long lastActivityTime, String string) { + lastActivityCache.put(lastActivityTime, string); + } +} diff --git a/xabber/src/main/java/com/xabber/android/data/roster/RosterManager.java b/xabber/src/main/java/com/xabber/android/data/roster/RosterManager.java index 4793441dc5..fe3258b822 100644 --- a/xabber/src/main/java/com/xabber/android/data/roster/RosterManager.java +++ b/xabber/src/main/java/com/xabber/android/data/roster/RosterManager.java @@ -17,6 +17,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; +import android.util.Log; import com.xabber.android.R; import com.xabber.android.data.Application; @@ -29,6 +30,8 @@ import com.xabber.android.data.connection.StanzaSender; import com.xabber.android.data.connection.listeners.OnDisconnectListener; import com.xabber.android.data.database.messagerealm.MessageItem; +import com.xabber.android.data.database.realm.ContactGroup; +import com.xabber.android.data.database.realm.ContactRealm; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.entity.NestedMap; import com.xabber.android.data.entity.UserJid; @@ -51,11 +54,13 @@ import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.Jid; +import org.jxmpp.stringprep.XmppStringprepException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -88,6 +93,35 @@ public static RosterManager getInstance() { return instance; } + public void onPreInitialize() { + List contacts = RosterCacheManager.loadContacts(); + for (ContactRealm contactRealm : contacts) { + try { + AccountJid account = AccountJid.from(contactRealm.getAccount() + "/" + contactRealm.getAccountResource()); + UserJid userJid = UserJid.from(contactRealm.getUser()); + RosterContact contact = RosterContact.getRosterContact(account, userJid, contactRealm.getName()); + + for (ContactGroup group : contactRealm.getGroups()) { + contact.addGroupReference(new RosterGroupReference(new RosterGroup(account, group.getGroupName()))); + } + + rosterContacts.put(contact.getAccount().toString(), + contact.getUser().getBareJid().toString(), contact); + + MessageItem lastMessage = contactRealm.getLastMessage(); + if (lastMessage != null) { + MessageManager.getInstance().getOrCreateChat(contact.getAccount(), contact.getUser(), lastMessage); + } else MessageManager.getInstance().getOrCreateChat(contact.getAccount(), contact.getUser()); + + } catch (UserJid.UserJidCreateException e) { + e.printStackTrace(); + } catch (XmppStringprepException e) { + e.printStackTrace(); + } + } + onContactsChanged(Collections.emptyList()); + } + @Nullable private Roster getRoster(AccountJid account) { final AccountItem accountItem = AccountManager.getInstance().getAccount(account); @@ -128,19 +162,22 @@ public boolean isSubscribed(AccountJid account, UserJid user) { } public Collection getAccountRosterContacts(final AccountJid accountJid) { - return Collections.unmodifiableCollection(rosterContacts.getNested(accountJid.toString()).values()); + List contactsCopy = new ArrayList<>(rosterContacts.getNested(accountJid.toString()).values()); + return Collections.unmodifiableCollection(contactsCopy); } public Collection getAllContacts() { - return Collections.unmodifiableCollection(rosterContacts.values()); + List contactsCopy = new ArrayList<>(); + Set keys = new HashSet<>(rosterContacts.keySet()); + for (String key : keys) { + contactsCopy.addAll(rosterContacts.getNested(key).values()); + } + return Collections.unmodifiableCollection(contactsCopy); } - void onContactsAdded(AccountJid account, Collection addresses) { + void onContactsAdded(final AccountJid account, Collection addresses) { final Roster roster = RosterManager.getInstance().getRoster(account); - - Collection newContacts = new ArrayList<>(addresses.size()); - - + final Collection newContacts = new ArrayList<>(addresses.size()); for (Jid jid : addresses) { RosterEntry entry = roster.getEntry(jid.asBareJid()); try { @@ -149,12 +186,18 @@ void onContactsAdded(AccountJid account, Collection addresses) { contact.getUser().getBareJid().toString(), contact); newContacts.add(contact); - LastActivityInteractor.getInstance().addJidToLastActivityQuery(account, UserJid.from(jid)); + LastActivityInteractor.getInstance().requestLastActivityAsync(account, UserJid.from(jid)); } catch (UserJid.UserJidCreateException e) { LogManager.exception(LOG_TAG, e); } } + Application.getInstance().runOnUiThread(new Runnable() { + @Override + public void run() { + RosterCacheManager.saveContact(account, newContacts); + } + }); onContactsChanged(newContacts); } @@ -163,7 +206,7 @@ void onContactsUpdated(AccountJid account, Collection addresses) { } void onContactsDeleted(AccountJid account, Collection addresses) { - Collection removedContacts = new ArrayList<>(addresses.size()); + final Collection removedContacts = new ArrayList<>(addresses.size()); for (Jid jid : addresses) { RosterContact contact = rosterContacts.remove(account.toString(), jid.asBareJid().toString()); @@ -171,6 +214,12 @@ void onContactsDeleted(AccountJid account, Collection addresses) { removedContacts.add(contact); } } + Application.getInstance().runOnUiThread(new Runnable() { + @Override + public void run() { + RosterCacheManager.removeContact(removedContacts); + } + }); onContactsChanged(removedContacts); } diff --git a/xabber/src/main/java/com/xabber/android/data/xaccount/AuthManager.java b/xabber/src/main/java/com/xabber/android/data/xaccount/AuthManager.java index 04541808f9..321b7181c0 100644 --- a/xabber/src/main/java/com/xabber/android/data/xaccount/AuthManager.java +++ b/xabber/src/main/java/com/xabber/android/data/xaccount/AuthManager.java @@ -348,7 +348,7 @@ public static String getProviderName(String provider) { case AuthManager.PROVIDER_FACEBOOK: return "Facebook"; default: - return "Google+"; + return "Google"; } } diff --git a/xabber/src/main/java/com/xabber/android/data/xaccount/HttpApiManager.java b/xabber/src/main/java/com/xabber/android/data/xaccount/HttpApiManager.java index e3522e1666..8a2411758f 100644 --- a/xabber/src/main/java/com/xabber/android/data/xaccount/HttpApiManager.java +++ b/xabber/src/main/java/com/xabber/android/data/xaccount/HttpApiManager.java @@ -5,6 +5,7 @@ import com.xabber.android.BuildConfig; import com.xabber.android.data.SettingsManager; import com.xabber.android.data.http.ICrowdfundingApi; +import com.xabber.android.data.http.IPushApi; import com.xabber.android.data.http.IXabberCom; import okhttp3.OkHttpClient; @@ -26,16 +27,19 @@ public class HttpApiManager { public static final String XABBER_API_URL = "https://api.xabber.com/api/v2/"; public static final String XABBER_DEV_API_URL = "https://api.dev.xabber.com/api/v2/"; private static final String XABBER_COM_URL = "https://www.xabber.com/"; + private static final String XABBER_PUSH_API_URL = "https://push.xabber.com/api/"; private static final String CROWDFUNDING_URL = "https://crowdfunding.xabber.com/api/v1/"; private static final String CROWDFUNDING_DEV_URL = "https://crowdfunding.dev.xabber.com/api/v1/"; private static IXabberApi xabberApi; private static IXabberCom xabberCom; private static ICrowdfundingApi crowdfundingApi; + private static IPushApi pushApi; private static Retrofit retrofit; private static Retrofit retrofitXabberCom; private static Retrofit retrofitCrowdfunding; + private static Retrofit retrofitPush; public static IXabberApi getXabberApi() { if (xabberApi == null) @@ -55,6 +59,12 @@ public static ICrowdfundingApi getCrowdfundingApi() { return crowdfundingApi; } + public static IPushApi getPushApi() { + if (pushApi == null) + pushApi = getPushRetrofit().create(IPushApi.class); + return pushApi; + } + public static Retrofit getRetrofit() { if (retrofit == null) { @@ -143,5 +153,33 @@ private static Retrofit getCrowdfundingRetrofit() { } return retrofitCrowdfunding; } + + private static Retrofit getPushRetrofit() { + if (retrofitPush == null) { + + HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); + loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); + + OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); + + // if debug enable http logging + if (BuildConfig.DEBUG) + httpClientBuilder.addInterceptor(loggingInterceptor); + + OkHttpClient httpClient = httpClientBuilder.build(); + + Gson gson = new GsonBuilder() + .setLenient() + .create(); + + retrofitPush = new Retrofit.Builder() + .baseUrl(XABBER_PUSH_API_URL) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create(gson)) + .client(httpClient) + .build(); + } + return retrofitPush; + } } diff --git a/xabber/src/main/java/com/xabber/android/presentation/mvp/contactlist/ContactListPresenter.java b/xabber/src/main/java/com/xabber/android/presentation/mvp/contactlist/ContactListPresenter.java index c2dfe3e619..14adf027e9 100644 --- a/xabber/src/main/java/com/xabber/android/presentation/mvp/contactlist/ContactListPresenter.java +++ b/xabber/src/main/java/com/xabber/android/presentation/mvp/contactlist/ContactListPresenter.java @@ -6,7 +6,6 @@ import com.xabber.android.R; import com.xabber.android.data.Application; import com.xabber.android.data.SettingsManager; -import com.xabber.android.data.account.AccountItem; import com.xabber.android.data.account.AccountManager; import com.xabber.android.data.account.CommonState; import com.xabber.android.data.account.listeners.OnAccountChangedListener; @@ -235,7 +234,7 @@ public void update() { if (!blockedUsers.contains(contact.getUser())) rosterContacts.add(contact); } else rosterContacts.add(contact); - } + } else rosterContacts.add(contact); } final boolean showOffline = SettingsManager.contactsShowOffline(); @@ -323,6 +322,7 @@ public void update() { // chats on top Collection chats = MessageManager.getInstance().getChatsOfEnabledAccount(); chatsGroup = getChatsGroup(chats, currentChatsState); + if (!chatsGroup.isEmpty()) hasVisibleContacts = true; // Build structure. for (RosterContact rosterContact : rosterContacts) { @@ -473,24 +473,19 @@ private GroupConfiguration getChatsGroup(Collection chats, ChatLis for (AbstractChat abstractChat : chats) { MessageItem lastMessage = abstractChat.getLastMessage(); - if (lastMessage != null) { - AccountItem accountItem = AccountManager.getInstance().getAccount(abstractChat.getAccount()); - if (accountItem != null && accountItem.isEnabled()) { - - switch (state) { - case unread: - if (!abstractChat.isArchived() && abstractChat.getUnreadMessageCount() > 0) - newChats.add(abstractChat); - break; - case archived: - if (abstractChat.isArchived()) newChats.add(abstractChat); - break; - default: - // recent - if (!abstractChat.isArchived()) newChats.add(abstractChat); - break; - } + switch (state) { + case unread: + if (!abstractChat.isArchived() && abstractChat.getUnreadMessageCount() > 0) + newChats.add(abstractChat); + break; + case archived: + if (abstractChat.isArchived()) newChats.add(abstractChat); + break; + default: + // recent + if (!abstractChat.isArchived()) newChats.add(abstractChat); + break; } } } diff --git a/xabber/src/main/java/com/xabber/android/presentation/mvp/contactlist/UpdateBackpressure.java b/xabber/src/main/java/com/xabber/android/presentation/mvp/contactlist/UpdateBackpressure.java index 3615b2f6ab..e7fa2959b5 100644 --- a/xabber/src/main/java/com/xabber/android/presentation/mvp/contactlist/UpdateBackpressure.java +++ b/xabber/src/main/java/com/xabber/android/presentation/mvp/contactlist/UpdateBackpressure.java @@ -10,7 +10,7 @@ public class UpdateBackpressure implements Runnable { - private static final long REFRESH_INTERVAL = 1000; + private static final long REFRESH_INTERVAL = 500; public interface UpdatableObject { void update(); diff --git a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountVO.java b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountVO.java index 91837173b8..81a151179a 100644 --- a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountVO.java +++ b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountVO.java @@ -16,7 +16,6 @@ import com.xabber.android.data.SettingsManager; import com.xabber.android.data.account.AccountItem; import com.xabber.android.data.account.AccountManager; -import com.xabber.android.data.account.StatusMode; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.extension.avatar.AvatarManager; import com.xabber.android.data.notification.custom_notification.CustomNotifyPrefsManager; @@ -39,7 +38,6 @@ public class AccountVO extends AbstractHeaderItem { private int accountColorIndicator; private int accountColorIndicatorBack; - private boolean showOfflineShadow; private String name; private String jid; @@ -61,7 +59,7 @@ public interface AccountClickListener { void onAccountMenuClick(int adapterPosition, View view); } - public AccountVO(int accountColorIndicator, int accountColorIndicatorBack, boolean showOfflineShadow, + public AccountVO(int accountColorIndicator, int accountColorIndicatorBack, String name, String jid, String status, int statusLevel, int statusId, Drawable avatar, int offlineModeLevel, String contactCount, AccountJid accountJid, boolean isExpand, String groupName, boolean isCustomNotification, @@ -69,7 +67,6 @@ public AccountVO(int accountColorIndicator, int accountColorIndicatorBack, boole this.id = UUID.randomUUID().toString(); this.accountColorIndicator = accountColorIndicator; this.accountColorIndicatorBack = accountColorIndicatorBack; - this.showOfflineShadow = showOfflineShadow; this.name = name; this.jid = jid; this.status = status; @@ -108,11 +105,6 @@ public AccountVO.ViewHolder createViewHolder(View view, FlexibleAdapter adapter) public void bindViewHolder(FlexibleAdapter adapter, ViewHolder viewHolder, int position, List payloads) { Context context = viewHolder.itemView.getContext(); - /** bind OFFLINE SHADOW */ - if (isShowOfflineShadow()) - viewHolder.offlineShadow.setVisibility(View.VISIBLE); - else viewHolder.offlineShadow.setVisibility(View.GONE); - /** set up ACCOUNT COLOR indicator */ viewHolder.accountColorIndicator.setBackgroundColor(getAccountColorIndicator()); viewHolder.accountColorIndicatorBack.setBackgroundColor(getAccountColorIndicatorBack()); @@ -177,7 +169,6 @@ public static AccountVO convert(AccountConfiguration configuration, AccountClick int statusId; Drawable avatar; int offlineModeLevel; - boolean showOfflineShadow = false; int accountColorIndicator; int accountColorIndicatorBack; String contactCount; @@ -214,20 +205,11 @@ public static AccountVO convert(AccountConfiguration configuration, AccountClick offlineModeLevel = showOfflineMode.ordinal(); - - StatusMode statusMode = accountItem.getDisplayStatusMode(); - - if (statusMode == StatusMode.unavailable || statusMode == StatusMode.connection) { - showOfflineShadow = true; - } else { - showOfflineShadow = false; - } - // custom notification boolean isCustomNotification = CustomNotifyPrefsManager.getInstance(). isPrefsExist(Key.createKey(account)); - return new AccountVO(accountColorIndicator, accountColorIndicatorBack, showOfflineShadow, + return new AccountVO(accountColorIndicator, accountColorIndicatorBack, name, jid, status, statusLevel, statusId, avatar, offlineModeLevel, contactCount, configuration.getAccount(), configuration.isExpanded(), configuration.getGroup(), isCustomNotification, listener); @@ -289,11 +271,6 @@ public int getAccountColorIndicatorBack() { return accountColorIndicatorBack; } - - public boolean isShowOfflineShadow() { - return showOfflineShadow; - } - public class ViewHolder extends ExpandableViewHolder { final ImageView ivAvatar; @@ -304,7 +281,6 @@ public class ViewHolder extends ExpandableViewHolder { final TextView tvContactCount; final ImageView ivStatus; final ImageView ivMenu; - final ImageView offlineShadow; final View accountColorIndicator; final View accountColorIndicatorBack; final View backgroundView; @@ -327,7 +303,6 @@ public ViewHolder(View view, FlexibleAdapter adapter, AccountClickListener liste tvContactCount = (TextView) view.findViewById(R.id.tvContactCount); ivStatus = (ImageView) view.findViewById(R.id.ivStatus); ivStatus.setOnClickListener(this); - offlineShadow = (ImageView) view.findViewById(R.id.offline_shadow); accountColorIndicator = view.findViewById(R.id.accountColorIndicator); accountColorIndicatorBack = view.findViewById(R.id.accountColorIndicatorBack); ivMenu = (ImageView) view.findViewById(R.id.ivMenu); diff --git a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountWithButtonsVO.java b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountWithButtonsVO.java index 1a2c547847..0eab37059b 100644 --- a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountWithButtonsVO.java +++ b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountWithButtonsVO.java @@ -22,14 +22,13 @@ public class AccountWithButtonsVO extends AccountVO implements IExpandable mSubItems; - public AccountWithButtonsVO(int accountColorIndicator, int accountColorIndicatorBack, - boolean showOfflineShadow, String name, + public AccountWithButtonsVO(int accountColorIndicator, int accountColorIndicatorBack, String name, String jid, String status, int statusLevel, int statusId, Drawable avatar, int offlineModeLevel, String contactCount, AccountJid accountJid, boolean isExpand, String groupName, boolean isCustomNotification, AccountClickListener listener) { - super(accountColorIndicator, accountColorIndicatorBack, showOfflineShadow, name, jid, + super(accountColorIndicator, accountColorIndicatorBack, name, jid, status, statusLevel, statusId, avatar, offlineModeLevel, contactCount, accountJid, isExpand, groupName, isCustomNotification, listener); @@ -76,7 +75,6 @@ public static AccountWithButtonsVO convert(AccountConfiguration configuration, A AccountVO contactVO = AccountVO.convert(configuration, listener); return new AccountWithButtonsVO( contactVO.getAccountColorIndicator(), contactVO.getAccountColorIndicatorBack(), - contactVO.isShowOfflineShadow(), contactVO.getName(), contactVO.getJid(), contactVO.getStatus(), contactVO.getStatusLevel(), contactVO.getStatusId(), contactVO.getAvatar(), contactVO.getOfflineModeLevel(), contactVO.getContactCount(), diff --git a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountWithContactsVO.java b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountWithContactsVO.java index 41ee5d7668..2080142cca 100644 --- a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountWithContactsVO.java +++ b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountWithContactsVO.java @@ -24,13 +24,13 @@ public class AccountWithContactsVO extends AccountVO implements IExpandable mSubItems; public AccountWithContactsVO(int accountColorIndicator, int accountColorIndicatorBack, - boolean showOfflineShadow, String name, + String name, String jid, String status, int statusLevel, int statusId, Drawable avatar, int offlineModeLevel, String contactCount, AccountJid accountJid, boolean isExpand, String groupName, boolean isCustomNotification, AccountClickListener listener) { - super(accountColorIndicator, accountColorIndicatorBack, showOfflineShadow, name, jid, + super(accountColorIndicator, accountColorIndicatorBack, name, jid, status, statusLevel, statusId, avatar, offlineModeLevel, contactCount, accountJid, isExpand, groupName, isCustomNotification, listener); @@ -79,7 +79,6 @@ public static AccountWithContactsVO convert(AccountConfiguration configuration, AccountVO contactVO = AccountVO.convert(configuration, listener); return new AccountWithContactsVO( contactVO.getAccountColorIndicator(), contactVO.getAccountColorIndicatorBack(), - contactVO.isShowOfflineShadow(), contactVO.getName(), contactVO.getJid(), contactVO.getStatus(), contactVO.getStatusLevel(), contactVO.getStatusId(), contactVO.getAvatar(), contactVO.getOfflineModeLevel(), contactVO.getContactCount(), diff --git a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountWithGroupsVO.java b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountWithGroupsVO.java index 272d4389c4..eb32459704 100644 --- a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountWithGroupsVO.java +++ b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/AccountWithGroupsVO.java @@ -23,13 +23,13 @@ public class AccountWithGroupsVO extends AccountVO implements IExpandable mSubItems; public AccountWithGroupsVO(int accountColorIndicator, int accountColorIndicatorBack, - boolean showOfflineShadow, String name, + String name, String jid, String status, int statusLevel, int statusId, Drawable avatar, int offlineModeLevel, String contactCount, AccountJid accountJid, boolean isExpand, String groupName, boolean isCustomNotification, AccountClickListener listener) { - super(accountColorIndicator, accountColorIndicatorBack, showOfflineShadow, name, jid, status, statusLevel, statusId, + super(accountColorIndicator, accountColorIndicatorBack, name, jid, status, statusLevel, statusId, avatar, offlineModeLevel, contactCount, accountJid, isExpand, groupName, isCustomNotification, listener); @@ -75,7 +75,6 @@ public static AccountWithGroupsVO convert(AccountConfiguration configuration, Ac AccountVO contactVO = AccountVO.convert(configuration, listener); return new AccountWithGroupsVO( contactVO.getAccountColorIndicator(), contactVO.getAccountColorIndicatorBack(), - contactVO.isShowOfflineShadow(), contactVO.getName(), contactVO.getJid(), contactVO.getStatus(), contactVO.getStatusLevel(), contactVO.getStatusId(), contactVO.getAvatar(), contactVO.getOfflineModeLevel(), contactVO.getContactCount(), diff --git a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ButtonVO.java b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ButtonVO.java index 54a510a112..72559566a6 100644 --- a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ButtonVO.java +++ b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ButtonVO.java @@ -3,12 +3,8 @@ import android.support.annotation.Nullable; import android.view.View; import android.widget.Button; -import android.widget.ImageView; import com.xabber.android.R; -import com.xabber.android.data.account.AccountItem; -import com.xabber.android.data.account.AccountManager; -import com.xabber.android.data.account.StatusMode; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.ui.adapter.contactlist.AccountConfiguration; import com.xabber.android.ui.color.ColorManager; @@ -31,18 +27,15 @@ public class ButtonVO extends AbstractFlexibleItem { private String id; private int accountColorIndicator; - private boolean showOfflineShadow; private String title; private String action; private AccountJid account; - public ButtonVO(int accountColorIndicator, boolean showOfflineShadow, - String title, String action, AccountJid account) { + public ButtonVO(int accountColorIndicator, String title, String action, AccountJid account) { this.id = UUID.randomUUID().toString(); this.accountColorIndicator = accountColorIndicator; - this.showOfflineShadow = showOfflineShadow; this.title = title; this.action = action; this.account = account; @@ -69,32 +62,18 @@ public ViewHolder createViewHolder(View view, FlexibleAdapter adapter) { @Override public void bindViewHolder(FlexibleAdapter adapter, ViewHolder holder, int position, List payloads) { - holder.btnListAction.setText(getTitle()); - - /** set up OFFLINE SHADOW */ - if (isShowOfflineShadow()) - holder.offlineShadow.setVisibility(View.VISIBLE); - else holder.offlineShadow.setVisibility(View.GONE); } public static ButtonVO convert(@Nullable AccountConfiguration configuration, String title, String action) { - boolean showOfflineShadow = false; int accountColorIndicator = ColorManager.getInstance().getAccountPainter().getDefaultMainColor(); AccountJid account = null; if (configuration != null) { account = configuration.getAccount(); accountColorIndicator = ColorManager.getInstance().getAccountPainter().getAccountMainColor(account); - - AccountItem accountItem = AccountManager.getInstance().getAccount(configuration.getAccount()); - if (accountItem != null) { - StatusMode statusMode = accountItem.getDisplayStatusMode(); - if (statusMode == StatusMode.unavailable || statusMode == StatusMode.connection) - showOfflineShadow = true; - } } - return new ButtonVO(accountColorIndicator, showOfflineShadow, title, action, account); + return new ButtonVO(accountColorIndicator, title, action, account); } public String getTitle() { @@ -105,10 +84,6 @@ public int getAccountColorIndicator() { return accountColorIndicator; } - public boolean isShowOfflineShadow() { - return showOfflineShadow; - } - public String getAction() { return action; } @@ -119,12 +94,10 @@ public AccountJid getAccount() { public class ViewHolder extends FlexibleViewHolder { final Button btnListAction; - final ImageView offlineShadow; public ViewHolder(View view, FlexibleAdapter adapter) { super(view, adapter); - offlineShadow = (ImageView) view.findViewById(R.id.offline_shadow); btnListAction = (Button) itemView.findViewById(R.id.btnListAction); btnListAction.setOnClickListener(this); } diff --git a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ChatVO.java b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ChatVO.java index 7f5be5d65e..d8c4c28d7a 100644 --- a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ChatVO.java +++ b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ChatVO.java @@ -35,7 +35,7 @@ public interface IsCurrentChatListener { boolean isCurrentChat(String account, String user); } - public ChatVO(int accountColorIndicator, int accountColorIndicatorBack, boolean showOfflineShadow, + public ChatVO(int accountColorIndicator, int accountColorIndicatorBack, String name, String status, int statusId, int statusLevel, Drawable avatar, int mucIndicatorLevel, UserJid userJid, AccountJid accountJid, int unreadCount, boolean mute, NotificationState.NotificationMode notificationMode, String messageText, @@ -44,7 +44,7 @@ public ChatVO(int accountColorIndicator, int accountColorIndicatorBack, boolean @Nullable IsCurrentChatListener currentChatListener, int forwardedCount, boolean isCustomNotification) { - super(accountColorIndicator, accountColorIndicatorBack, showOfflineShadow, name, status, + super(accountColorIndicator, accountColorIndicatorBack, name, status, statusId, statusLevel, avatar, mucIndicatorLevel, userJid, accountJid, unreadCount, mute, notificationMode, messageText, isOutgoing, time, messageStatus, messageOwner, archived, lastActivity, listener, forwardedCount, isCustomNotification); @@ -57,7 +57,6 @@ public static ChatVO convert(AbstractContact contact, ContactClickListener liste ExtContactVO contactVO = ExtContactVO.convert(contact, listener); return new ChatVO( contactVO.getAccountColorIndicator(), contactVO.getAccountColorIndicatorBack(), - contactVO.isShowOfflineShadow(), contactVO.getName(), contactVO.getStatus(), contactVO.getStatusId(), contactVO.getStatusLevel(), contactVO.getAvatar(), contactVO.getMucIndicatorLevel(), contactVO.getUserJid(), contactVO.getAccountJid(), contactVO.getUnreadCount(), diff --git a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ChatWithButtonVO.java b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ChatWithButtonVO.java index c1ad372ff4..3b3828d8c2 100644 --- a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ChatWithButtonVO.java +++ b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ChatWithButtonVO.java @@ -17,7 +17,6 @@ public class ChatWithButtonVO extends ExtContactVO { public ChatWithButtonVO(int accountColorIndicator, int accountColorIndicatorBack, - boolean showOfflineShadow, String name, String status, int statusId, int statusLevel, Drawable avatar, int mucIndicatorLevel, UserJid userJid, AccountJid accountJid, int unreadCount, boolean mute, NotificationState.NotificationMode notificationMode, String messageText, @@ -25,7 +24,7 @@ public ChatWithButtonVO(int accountColorIndicator, int accountColorIndicatorBack boolean archived, String lastActivity, ContactClickListener listener, int forwardedCount, boolean isCustomNotification) { - super(accountColorIndicator, accountColorIndicatorBack, showOfflineShadow, name, status, + super(accountColorIndicator, accountColorIndicatorBack, name, status, statusId, statusLevel, avatar, mucIndicatorLevel, userJid, accountJid, unreadCount, mute, notificationMode, messageText, isOutgoing, time, messageStatus, messageOwner, archived, lastActivity, listener, forwardedCount, isCustomNotification); @@ -35,7 +34,6 @@ public static ChatWithButtonVO convert(AbstractContact contact, ContactClickList ExtContactVO contactVO = ExtContactVO.convert(contact, listener); return new ChatWithButtonVO( contactVO.getAccountColorIndicator(), contactVO.getAccountColorIndicatorBack(), - contactVO.isShowOfflineShadow(), contactVO.getName(), contactVO.getStatus(), contactVO.getStatusId(), contactVO.getStatusLevel(), contactVO.getAvatar(), contactVO.getMucIndicatorLevel(), contactVO.getUserJid(), contactVO.getAccountJid(), contactVO.getUnreadCount(), @@ -48,7 +46,6 @@ public static ChatWithButtonVO convert(AbstractContact contact, ContactClickList public static ChatWithButtonVO convert(ChatVO chat) { return new ChatWithButtonVO( chat.getAccountColorIndicator(), chat.getAccountColorIndicatorBack(), - chat.isShowOfflineShadow(), chat.getName(), chat.getStatus(), chat.getStatusId(), chat.getStatusLevel(), chat.getAvatar(), chat.getMucIndicatorLevel(), chat.getUserJid(), chat.getAccountJid(), chat.getUnreadCount(), diff --git a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ContactVO.java b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ContactVO.java index 65ffddbf6d..3397c563fc 100644 --- a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ContactVO.java +++ b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ContactVO.java @@ -59,7 +59,6 @@ public class ContactVO extends AbstractFlexibleItem { private int accountColorIndicator; private int accountColorIndicatorBack; - private boolean showOfflineShadow; private final String name; private final String status; @@ -91,7 +90,7 @@ public interface ContactClickListener { } protected ContactVO(int accountColorIndicator, int accountColorIndicatorBack, - boolean showOfflineShadow, String name, + String name, String status, int statusId, int statusLevel, Drawable avatar, int mucIndicatorLevel, UserJid userJid, AccountJid accountJid, int unreadCount, boolean mute, NotificationState.NotificationMode notificationMode, String messageText, @@ -101,7 +100,6 @@ protected ContactVO(int accountColorIndicator, int accountColorIndicatorBack, this.id = UUID.randomUUID().toString(); this.accountColorIndicator = accountColorIndicator; this.accountColorIndicatorBack = accountColorIndicatorBack; - this.showOfflineShadow = showOfflineShadow; this.name = name; this.status = status; this.statusId = statusId; @@ -126,7 +124,6 @@ protected ContactVO(int accountColorIndicator, int accountColorIndicatorBack, } public static ContactVO convert(AbstractContact contact, ContactClickListener listener) { - boolean showOfflineShadow; int accountColorIndicator; int accountColorIndicatorBack; Drawable avatar; @@ -139,18 +136,11 @@ public static ContactVO convert(AbstractContact contact, ContactClickListener li int forwardedCount = 0; String messageOwner = null; - AccountItem accountItem = AccountManager.getInstance().getAccount(contact.getAccount()); - if (accountItem != null && accountItem.getState() == ConnectionState.connected) { - showOfflineShadow = false; - } else { - showOfflineShadow = true; - } - accountColorIndicator = ColorManager.getInstance().getAccountPainter() .getAccountMainColor(contact.getAccount()); accountColorIndicatorBack = ColorManager.getInstance().getAccountPainter() .getAccountIndicatorBackColor(contact.getAccount()); - avatar = contact.getAvatarForContactList(); + avatar = contact.getAvatar(); String name = contact.getName(); @@ -176,7 +166,7 @@ public static ContactVO convert(AbstractContact contact, ContactClickListener li AbstractChat chat = messageManager.getOrCreateChat(contact.getAccount(), contact.getUser()); MessageItem lastMessage = chat.getLastMessage(); - if (lastMessage == null) { + if (lastMessage == null || lastMessage.getText() == null) { messageText = statusText; } else { if (lastMessage.haveAttachments() && lastMessage.getAttachments().size() > 0) { @@ -200,17 +190,19 @@ public static ContactVO convert(AbstractContact contact, ContactClickListener li messageOwner = lastMessage.getResource().toString(); // message status - if (lastMessage.isError()) { - messageStatus = 4; - } else if (!MessageItem.isUploadFileMessage(lastMessage) && !lastMessage.isSent() - && System.currentTimeMillis() - lastMessage.getTimestamp() > 1000) { - messageStatus = 5; - } else if (lastMessage.isDisplayed() || lastMessage.isReceivedFromMessageArchive()) { - messageStatus = 1; - } else if (lastMessage.isDelivered() || lastMessage.isForwarded()) { - messageStatus = 2; - } else if (lastMessage.isAcknowledged()) { - messageStatus = 3; + if (isOutgoing) { + if (!MessageItem.isUploadFileMessage(lastMessage) && !lastMessage.isSent() + && System.currentTimeMillis() - lastMessage.getTimestamp() > 1000) { + messageStatus = 5; + } else if (lastMessage.isDisplayed() || lastMessage.isReceivedFromMessageArchive()) { + messageStatus = 1; + } else if (lastMessage.isDelivered() || lastMessage.isForwarded()) { + messageStatus = 2; + } else if (lastMessage.isError()) { + messageStatus = 4; + } else if (lastMessage.isAcknowledged()) { + messageStatus = 3; + } } // forwarded @@ -228,7 +220,7 @@ public static ContactVO convert(AbstractContact contact, ContactClickListener li isPrefsExist(Key.createKey(contact.getAccount(), contact.getUser())); return new ContactVO(accountColorIndicator, accountColorIndicatorBack, - showOfflineShadow, name, statusText, statusId, + name, statusText, statusId, statusLevel, avatar, mucIndicatorLevel, contact.getUser(), contact.getAccount(), unreadCount, !chat.notifyAboutMessage(), mode, messageText, isOutgoing, time, messageStatus, messageOwner, chat.isArchived(), lastActivity, listener, forwardedCount, @@ -266,11 +258,6 @@ public ViewHolder createViewHolder(View view, FlexibleAdapter adapter) { public void bindViewHolder(FlexibleAdapter adapter, ViewHolder viewHolder, int position, List payloads) { Context context = viewHolder.itemView.getContext(); - /** set up OFFLINE SHADOW */ - if (isShowOfflineShadow()) - viewHolder.offlineShadow.setVisibility(View.VISIBLE); - else viewHolder.offlineShadow.setVisibility(View.GONE); - /** set up ACCOUNT COLOR indicator */ viewHolder.accountColorIndicator.setBackgroundColor(getAccountColorIndicator()); viewHolder.accountColorIndicatorBack.setBackgroundColor(getAccountColorIndicatorBack()); @@ -432,10 +419,6 @@ public int getAccountColorIndicatorBack() { return accountColorIndicatorBack; } - public boolean isShowOfflineShadow() { - return showOfflineShadow; - } - public String getLastActivity() { return lastActivity; } @@ -467,7 +450,6 @@ public class ViewHolder extends FlexibleViewHolder implements View.OnCreateConte final TextView tvMessageText; final TextView tvTime; final ImageView ivMessageStatus; - final ImageView offlineShadow; final TextView tvUnreadCount; public final TextView tvAction; public final TextView tvActionLeft; @@ -493,7 +475,6 @@ public ViewHolder(View view, FlexibleAdapter adapter, ContactClickListener liste tvMessageText = (TextView) view.findViewById(R.id.tvMessageText); tvTime = (TextView) view.findViewById(R.id.tvTime); ivMessageStatus = (ImageView) view.findViewById(R.id.ivMessageStatus); - offlineShadow = (ImageView) view.findViewById(R.id.offline_shadow); tvUnreadCount = (TextView) view.findViewById(R.id.tvUnreadCount); foregroundView = (LinearLayout) view.findViewById(R.id.foregroundView); tvAction = (TextView) view.findViewById(R.id.tvAction); diff --git a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ExtContactVO.java b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ExtContactVO.java index f7a79371ca..d5dc357b06 100644 --- a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ExtContactVO.java +++ b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/ExtContactVO.java @@ -26,7 +26,7 @@ public class ExtContactVO extends ContactVO { public ExtContactVO(int accountColorIndicator, int accountColorIndicatorBack, - boolean showOfflineShadow, String name, + String name, String status, int statusId, int statusLevel, Drawable avatar, int mucIndicatorLevel, UserJid userJid, AccountJid accountJid, int unreadCount, boolean mute, NotificationState.NotificationMode notificationMode, String messageText, @@ -34,7 +34,7 @@ public ExtContactVO(int accountColorIndicator, int accountColorIndicatorBack, boolean archived, String lastActivity, ContactClickListener listener, int forwardedCount, boolean isCustomNotification) { - super(accountColorIndicator, accountColorIndicatorBack, showOfflineShadow, name, status, + super(accountColorIndicator, accountColorIndicatorBack, name, status, statusId, statusLevel, avatar, mucIndicatorLevel, userJid, accountJid, unreadCount, mute, notificationMode, messageText, isOutgoing, time, messageStatus, messageOwner, archived, lastActivity, listener, forwardedCount, @@ -45,7 +45,6 @@ public static ExtContactVO convert(AbstractContact contact, ContactClickListener ContactVO contactVO = ContactVO.convert(contact, listener); return new ExtContactVO( contactVO.getAccountColorIndicator(), contactVO.getAccountColorIndicatorBack(), - contactVO.isShowOfflineShadow(), contactVO.getName(), contactVO.getStatus(), contactVO.getStatusId(), contactVO.getStatusLevel(), contactVO.getAvatar(), contactVO.getMucIndicatorLevel(), contactVO.getUserJid(), contactVO.getAccountJid(), contactVO.getUnreadCount(), diff --git a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/GroupVO.java b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/GroupVO.java index 73f7b56c26..bc49409548 100644 --- a/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/GroupVO.java +++ b/xabber/src/main/java/com/xabber/android/presentation/ui/contactlist/viewobjects/GroupVO.java @@ -43,7 +43,6 @@ public class GroupVO extends AbstractFlexibleItem private int accountColorIndicator; private int accountColorIndicatorBack; - private boolean showOfflineShadow; private String title; private int offlineIndicatorLevel; @@ -63,7 +62,7 @@ public interface GroupClickListener { } public GroupVO(int accountColorIndicator, int accountColorIndicatorBack, - boolean showOfflineShadow, String title, + String title, boolean expanded, int offlineIndicatorLevel, String groupName, AccountJid accountJid, boolean firstInAccount, boolean isCustomNotification, GroupClickListener listener) { @@ -71,7 +70,6 @@ public GroupVO(int accountColorIndicator, int accountColorIndicatorBack, this.id = UUID.randomUUID().toString(); this.accountColorIndicator = accountColorIndicator; this.accountColorIndicatorBack = accountColorIndicatorBack; - this.showOfflineShadow = showOfflineShadow; this.title = title; this.mExpanded = expanded; this.offlineIndicatorLevel = offlineIndicatorLevel; @@ -104,11 +102,6 @@ public ViewHolder createViewHolder(View view, FlexibleAdapter adapter) { @Override public void bindViewHolder(FlexibleAdapter adapter, ViewHolder viewHolder, int position, List payloads) { - /** set up OFFLINE SHADOW */ - if (isShowOfflineShadow()) - viewHolder.offlineShadow.setVisibility(View.VISIBLE); - else viewHolder.offlineShadow.setVisibility(View.GONE); - /** set up ACCOUNT COLOR indicator */ viewHolder.accountColorIndicator.setBackgroundColor(getAccountColorIndicator()); viewHolder.accountColorIndicatorBack.setBackgroundColor(getAccountColorIndicatorBack()); @@ -182,7 +175,6 @@ public static GroupVO convert(GroupConfiguration configuration, boolean firstInA String name = GroupManager.getInstance().getGroupName(configuration.getAccount(), configuration.getGroup()); - boolean showOfflineShadow = false; int accountColorIndicator; int accountColorIndicatorBack; boolean expanded; @@ -207,15 +199,7 @@ public static GroupVO convert(GroupConfiguration configuration, boolean firstInA if (!name.equals(RECENT_CHATS_TITLE)) name = String.format("%s (%d/%d)", name, configuration.getOnline(), configuration.getTotal()); - AccountItem accountItem = AccountManager.getInstance().getAccount(configuration.getAccount()); - - if (accountItem != null) { - StatusMode statusMode = accountItem.getDisplayStatusMode(); - if (statusMode == StatusMode.unavailable || statusMode == StatusMode.connection) - showOfflineShadow = true; - } - - return new GroupVO(accountColorIndicator, accountColorIndicatorBack, showOfflineShadow, name, expanded, + return new GroupVO(accountColorIndicator, accountColorIndicatorBack, name, expanded, offlineIndicatorLevel, configuration.getGroup(), configuration.getAccount(), firstInAccount, isCustomNotification, listener); } @@ -248,16 +232,11 @@ public int getAccountColorIndicatorBack() { return accountColorIndicatorBack; } - public boolean isShowOfflineShadow() { - return showOfflineShadow; - } - public class ViewHolder extends ExpandableViewHolder implements View.OnCreateContextMenuListener { final ImageView indicator; final TextView name; final ImageView groupOfflineIndicator; - final ImageView offlineShadow; final View accountColorIndicator; final View accountColorIndicatorBack; final View line; @@ -273,7 +252,6 @@ public ViewHolder(View view, FlexibleAdapter adapter) { indicator = (ImageView) view.findViewById(R.id.indicator); name = (TextView) view.findViewById(R.id.name); groupOfflineIndicator = (ImageView) view.findViewById(R.id.group_offline_indicator); - offlineShadow = (ImageView) view.findViewById(R.id.offline_shadow); line = view.findViewById(R.id.line); } diff --git a/xabber/src/main/java/com/xabber/android/receiver/BootReceiver.java b/xabber/src/main/java/com/xabber/android/receiver/BootReceiver.java index 31a34e77ab..8153c145a9 100644 --- a/xabber/src/main/java/com/xabber/android/receiver/BootReceiver.java +++ b/xabber/src/main/java/com/xabber/android/receiver/BootReceiver.java @@ -17,11 +17,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.os.Build; import com.xabber.android.data.SettingsManager; -import com.xabber.android.data.notification.NotificationManager; -import com.xabber.android.service.XabberService; +import com.xabber.android.utils.Utils; /** * Android boot receiver. @@ -33,9 +31,7 @@ public class BootReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (SettingsManager.connectionStartAtBoot()) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - context.startForegroundService(XabberService.createIntent(context)); - else context.startService(XabberService.createIntent(context)); + Utils.startXabberServiceCompat(context); } else { android.os.Process.killProcess(android.os.Process.myPid()); } diff --git a/xabber/src/main/java/com/xabber/android/receiver/GoAwayReceiver.java b/xabber/src/main/java/com/xabber/android/receiver/GoAwayReceiver.java index 64055253dd..3769d0cdfc 100644 --- a/xabber/src/main/java/com/xabber/android/receiver/GoAwayReceiver.java +++ b/xabber/src/main/java/com/xabber/android/receiver/GoAwayReceiver.java @@ -18,6 +18,7 @@ import android.content.Context; import android.content.Intent; +import com.xabber.android.data.Application; import com.xabber.android.data.account.AccountManager; /** @@ -29,7 +30,12 @@ public class GoAwayReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - AccountManager.getInstance().goAway(); + Application.getInstance().runInBackgroundUserRequest(new Runnable() { + @Override + public void run() { + AccountManager.getInstance().goAway(); + } + }); } public static Intent createIntent(Context context) { diff --git a/xabber/src/main/java/com/xabber/android/receiver/GoXaReceiver.java b/xabber/src/main/java/com/xabber/android/receiver/GoXaReceiver.java index b48d98a7b8..2ccb269d2f 100644 --- a/xabber/src/main/java/com/xabber/android/receiver/GoXaReceiver.java +++ b/xabber/src/main/java/com/xabber/android/receiver/GoXaReceiver.java @@ -18,6 +18,7 @@ import android.content.Context; import android.content.Intent; +import com.xabber.android.data.Application; import com.xabber.android.data.account.AccountManager; /** @@ -29,7 +30,12 @@ public class GoXaReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - AccountManager.getInstance().goXa(); + Application.getInstance().runInBackgroundUserRequest(new Runnable() { + @Override + public void run() { + AccountManager.getInstance().goXa(); + } + }); } public static Intent createIntent(Context context) { diff --git a/xabber/src/main/java/com/xabber/android/receiver/NotificationReceiver.java b/xabber/src/main/java/com/xabber/android/receiver/NotificationReceiver.java index 69e907b81a..ef7ddb9e37 100644 --- a/xabber/src/main/java/com/xabber/android/receiver/NotificationReceiver.java +++ b/xabber/src/main/java/com/xabber/android/receiver/NotificationReceiver.java @@ -5,13 +5,20 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.os.Parcelable; import android.support.v4.app.RemoteInput; +import com.xabber.android.data.Application; +import com.xabber.android.data.entity.AccountJid; +import com.xabber.android.data.notification.Action; import com.xabber.android.data.notification.MessageNotificationManager; +import com.xabber.android.data.push.SyncManager; +import com.xabber.android.utils.Utils; public class NotificationReceiver extends BroadcastReceiver { private static final String KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID"; + private static final String KEY_ACCOUNT_JID = "KEY_ACCOUNT_JID"; public static final String KEY_REPLY_TEXT = "KEY_REPLY_TEXT"; private static final String ACTION_CANCEL = "ACTION_CANCEL"; @@ -21,50 +28,68 @@ public class NotificationReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); + AccountJid accountJid = intent.getParcelableExtra(KEY_ACCOUNT_JID); + if (!Application.getInstance().isServiceStarted()) { + MessageNotificationManager.getInstance().onDelayedNotificationAction(createAction(intent)); + if (accountJid != null) + Utils.startXabberServiceCompatWithSyncMode(context, accountJid); + + } else { + if (!SyncManager.getInstance().isAccountAllowed(accountJid)) + SyncManager.getInstance().addAllowedAccount(accountJid); + + MessageNotificationManager.getInstance().onNotificationAction(createAction(intent)); + } + } + + private Action createAction(Intent intent) { int notificationId = intent.getIntExtra(KEY_NOTIFICATION_ID, 1); + String action = intent.getAction(); - if (action == null) return; + if (action == null) + return new Action(notificationId, Action.ActionType.cancel); switch (action) { case ACTION_MUTE: - MessageNotificationManager.getInstance().onNotificationMuted(notificationId); - break; - case ACTION_CANCEL: - MessageNotificationManager.getInstance().onNotificationCanceled(notificationId); - break; + return new Action(notificationId, Action.ActionType.snooze); + case ACTION_REPLY: + CharSequence reply = null; Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); - if (remoteInput != null) - MessageNotificationManager.getInstance().onNotificationReplied(notificationId, - remoteInput.getCharSequence(KEY_REPLY_TEXT)); - break; + if (remoteInput != null) reply = remoteInput.getCharSequence(KEY_REPLY_TEXT); + return new Action(notificationId, reply, Action.ActionType.reply); + case ACTION_MARK_AS_READ: - MessageNotificationManager.getInstance().onNotificationMarkedAsRead(notificationId); - break; + return new Action(notificationId, Action.ActionType.read); + + default: // ACTION_CANCEL + return new Action(notificationId, Action.ActionType.cancel); } } - public static PendingIntent createReplyIntent(Context context, int notificationId) { + public static PendingIntent createReplyIntent(Context context, int notificationId, AccountJid accountJid) { Intent intent = new Intent(context, NotificationReceiver.class); intent.setAction(NotificationReceiver.ACTION_REPLY); intent.putExtra(KEY_NOTIFICATION_ID, notificationId); + intent.putExtra(KEY_ACCOUNT_JID, (Parcelable) accountJid); return PendingIntent.getBroadcast(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT); } - public static PendingIntent createMarkAsReadIntent(Context context, int notificationId) { + public static PendingIntent createMarkAsReadIntent(Context context, int notificationId, AccountJid accountJid) { Intent intent = new Intent(context, NotificationReceiver.class); intent.setAction(NotificationReceiver.ACTION_MARK_AS_READ); intent.putExtra(KEY_NOTIFICATION_ID, notificationId); + intent.putExtra(KEY_ACCOUNT_JID, (Parcelable) accountJid); return PendingIntent.getBroadcast(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT); } - public static PendingIntent createMuteIntent(Context context, int notificationId) { + public static PendingIntent createMuteIntent(Context context, int notificationId, AccountJid accountJid) { Intent intent = new Intent(context, NotificationReceiver.class); intent.setAction(NotificationReceiver.ACTION_MUTE); intent.putExtra(KEY_NOTIFICATION_ID, notificationId); + intent.putExtra(KEY_ACCOUNT_JID, (Parcelable) accountJid); return PendingIntent.getBroadcast(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT); } diff --git a/xabber/src/main/java/com/xabber/android/service/PushService.java b/xabber/src/main/java/com/xabber/android/service/PushService.java index 2afb8391c3..b3221f393a 100644 --- a/xabber/src/main/java/com/xabber/android/service/PushService.java +++ b/xabber/src/main/java/com/xabber/android/service/PushService.java @@ -6,6 +6,7 @@ import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; import com.google.gson.Gson; +import com.xabber.android.data.push.PushManager; import com.xabber.android.data.xaccount.XabberAccount; import com.xabber.android.data.xaccount.XabberAccountManager; @@ -14,13 +15,15 @@ public class PushService extends FirebaseMessagingService { private static final String FIELD_TARGET_TYPE = "target_type"; - private static final String FIELD_TARGET = "target"; private static final String FIELD_BODY = "body"; private static final String ACTION_SETTINGS_UPDATED = "settings_updated"; private static final String ACTION_ACCOUNT_UPDATED = "account_updated"; + private static final String ACTION_REGJID = "regjid"; + private static final String ACTION_MESSAGE = "message"; private static final String TARGET_TYPE_XACCOUNT = "xaccount"; private static final String TARGET_TYPE_NODE = "node"; + private static final String REGJID_SUCCESS_RESULT = "success"; Gson gson = new Gson(); @@ -31,18 +34,17 @@ public void onMessageReceived(RemoteMessage remoteMessage) { if (remoteMessage.getData().size() > 0) { Map data = remoteMessage.getData(); String targetType = data.get(FIELD_TARGET_TYPE); - String target = data.get(FIELD_TARGET); String encodedBody = data.get(FIELD_BODY); - if (targetType != null && target != null && encodedBody != null) { + if (targetType != null && encodedBody != null) { String decodedBody = new String(Base64.decode(encodedBody, Base64.NO_WRAP)); switch (targetType) { case TARGET_TYPE_XACCOUNT: - onXAccountPushReceived(target, decodedBody); + onXAccountPushReceived(decodedBody); break; case TARGET_TYPE_NODE: - onXMPPPushReceived(target, decodedBody); + onXMPPPushReceived(decodedBody); break; default: Log.d(PushService.class.getSimpleName(), "Unexpected target type - " + targetType); @@ -51,32 +53,46 @@ public void onMessageReceived(RemoteMessage remoteMessage) { } } - private void onXAccountPushReceived(String target, String body) { + private void onXAccountPushReceived(String body) { + XAccountPushData data = gson.fromJson(body, XAccountPushData.class); XabberAccount xabberAccount = XabberAccountManager.getInstance().getAccount(); - if (xabberAccount != null && xabberAccount.getFullUsername().equals(target)) { - XAccountPushData data = gson.fromJson(body, XAccountPushData.class); - - if (data != null && xabberAccount.getFullUsername().equals(data.getUsername()) - && !xabberAccount.getToken().equals(data.getFromToken())) { - switch (data.getAction()) { - case ACTION_SETTINGS_UPDATED: - XabberAccountManager.getInstance().updateAccountSettings(); - // used async function updateAccountSettings - // inside function exist check that prevents simultaneous calls - break; - case ACTION_ACCOUNT_UPDATED: - XabberAccountManager.getInstance().updateAccountInfo(); - break; - default: - Log.d(PushService.class.getSimpleName(), - "Unexpected action in Xabber Account push - " + data.getAction()); - } + + if (xabberAccount != null && data != null + && xabberAccount.getFullUsername().equals(data.getUsername()) + && !xabberAccount.getToken().equals(data.getFromToken())) { + + switch (data.getAction()) { + case ACTION_SETTINGS_UPDATED: + XabberAccountManager.getInstance().updateAccountSettings(); + // used async function updateAccountSettings + // inside function exist check that prevents simultaneous calls + break; + case ACTION_ACCOUNT_UPDATED: + XabberAccountManager.getInstance().updateAccountInfo(); + break; + default: + Log.d(PushService.class.getSimpleName(), + "Unexpected action in Xabber Account push - " + data.getAction()); } } } - private void onXMPPPushReceived(String target, String body) { - /** Will be used for XMPP pushes */ + private void onXMPPPushReceived(String body) { + EndpointRegPushData data = gson.fromJson(body, EndpointRegPushData.class); + if (data != null) { + switch (data.getAction()) { + case ACTION_REGJID: + if (REGJID_SUCCESS_RESULT.equals(data.getResult())) + PushManager.getInstance().onEndpointRegistered(data.getJid(), data.getService(), data.getNode()); + break; + case ACTION_MESSAGE: + PushManager.getInstance().onNewMessagePush(this, data.getNode()); + break; + default: + Log.d(PushService.class.getSimpleName(), + "Unexpected action in Node push - " + data.getAction()); + } + } } private static class XAccountPushData { @@ -102,4 +118,40 @@ public String getFromToken() { return from_token; } } + + private static class EndpointRegPushData { + private final String action; + private final String result; + private final String jid; + private final String node; + private final String service; + + public EndpointRegPushData(String action, String result, String jid, String node, String service) { + this.action = action; + this.result = result; + this.jid = jid; + this.node = node; + this.service = service; + } + + public String getAction() { + return action; + } + + public String getResult() { + return result; + } + + public String getJid() { + return jid; + } + + public String getNode() { + return node; + } + + public String getService() { + return service; + } + } } diff --git a/xabber/src/main/java/com/xabber/android/service/UploadService.java b/xabber/src/main/java/com/xabber/android/service/UploadService.java index ebee560278..64e5d8cb74 100644 --- a/xabber/src/main/java/com/xabber/android/service/UploadService.java +++ b/xabber/src/main/java/com/xabber/android/service/UploadService.java @@ -11,6 +11,7 @@ import android.support.annotation.Nullable; import android.webkit.MimeTypeMap; +import com.xabber.android.data.SettingsManager; import com.xabber.android.data.account.AccountItem; import com.xabber.android.data.account.AccountManager; import com.xabber.android.data.entity.AccountJid; @@ -202,7 +203,7 @@ private void startWork(AccountJid account, UserJid user, List filePaths, final File file; // compress file if image - if (FileManager.fileIsImage(uncompressedFile)) { + if (FileManager.fileIsImage(uncompressedFile) && SettingsManager.connectionCompressImage()) { file = ImageCompressor.compressImage(uncompressedFile, getCompressedDirPath()); if (file == null) throw new Exception("Compress image failed"); diff --git a/xabber/src/main/java/com/xabber/android/service/XabberService.java b/xabber/src/main/java/com/xabber/android/service/XabberService.java index 1bb68f3fff..a27a65ff63 100644 --- a/xabber/src/main/java/com/xabber/android/service/XabberService.java +++ b/xabber/src/main/java/com/xabber/android/service/XabberService.java @@ -20,10 +20,12 @@ import android.os.IBinder; import com.xabber.android.data.Application; +import com.xabber.android.data.account.AccountItem; import com.xabber.android.data.account.AccountManager; +import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.log.LogManager; -import com.xabber.android.data.SettingsManager; import com.xabber.android.data.notification.NotificationManager; +import com.xabber.android.data.push.SyncManager; /** * Basic service to work in background. @@ -48,7 +50,7 @@ public void onCreate() { public void changeForeground() { LogManager.i(this, "changeForeground"); - if (SettingsManager.eventsPersistent() + if (needForeground() && Application.getInstance().isInitialized() && !AccountManager.getInstance().getEnabledAccounts().isEmpty()) { startForeground(NotificationManager.PERSISTENT_NOTIFICATION_ID, @@ -63,6 +65,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { int result = super.onStartCommand(intent, flags, startId); LogManager.i(this, "onStartCommand"); Application.getInstance().onServiceStarted(); + SyncManager.getInstance().onServiceStarted(intent); return result; } @@ -83,4 +86,16 @@ public static Intent createIntent(Context context) { return new Intent(context, XabberService.class); } + public boolean needForeground() { + if (SyncManager.getInstance().isSyncPeriod()) return true; + for (AccountJid accountJid : AccountManager.getInstance().getEnabledAccounts()) { + AccountItem accountItem = AccountManager.getInstance().getAccount(accountJid); + if (accountItem != null) { + if (!accountItem.isPushWasEnabled() + && SyncManager.getInstance().isAccountNeedConnection(accountItem)) + return true; + } + } + return false; + } } diff --git a/xabber/src/main/java/com/xabber/android/ui/activity/AccountActivity.java b/xabber/src/main/java/com/xabber/android/ui/activity/AccountActivity.java index 6521eb62a6..ff87f9b8ca 100644 --- a/xabber/src/main/java/com/xabber/android/ui/activity/AccountActivity.java +++ b/xabber/src/main/java/com/xabber/android/ui/activity/AccountActivity.java @@ -147,7 +147,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { RecyclerView recyclerView = (RecyclerView) findViewById(R.id.account_options_recycler_view); - accountOptionsAdapter = new AccountOptionsAdapter(AccountOption.values(), this, accountItem); + accountOptionsAdapter = new AccountOptionsAdapter(AccountOption.getValues(), this, accountItem); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setAdapter(accountOptionsAdapter); @@ -167,6 +167,9 @@ private void updateOptions() { AccountOption.CONNECTION_SETTINGS.setDescription(account.getFullJid().asBareJid().toString()); + AccountOption.PUSH_NOTIFICATIONS.setDescription(getString(accountItem.isPushWasEnabled() + ? R.string.account_push_state_enabled : R.string.account_push_state_disabled)); + AccountOption.COLOR.setDescription(ColorManager.getInstance().getAccountPainter().getAccountColorName(account)); updateBlockListOption(); @@ -191,7 +194,7 @@ private void updateBlockListOption() { } else if (!supported) { description = getString(R.string.blocked_contacts_not_supported); } else { - int size = blockingManager.getBlockedContacts(account).size(); + int size = blockingManager.getCachedBlockedContacts(account).size(); if (size == 0) { description = getString(R.string.blocked_contacts_empty); } else { @@ -246,6 +249,9 @@ public void onAccountOptionClick(AccountOption option) { case CONNECTION_SETTINGS: startAccountSettingsActivity(); break; + case PUSH_NOTIFICATIONS: + startActivity(AccountPushActivity.createIntent(this, account)); + break; case COLOR: AccountColorDialog.newInstance(account).show(getFragmentManager(), AccountColorDialog.class.getSimpleName()); diff --git a/xabber/src/main/java/com/xabber/android/ui/activity/AccountPushActivity.java b/xabber/src/main/java/com/xabber/android/ui/activity/AccountPushActivity.java new file mode 100644 index 0000000000..95c41ee60b --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/ui/activity/AccountPushActivity.java @@ -0,0 +1,129 @@ +package com.xabber.android.ui.activity; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.View; +import android.widget.RelativeLayout; +import android.widget.Switch; +import android.widget.TextView; + +import com.xabber.android.R; +import com.xabber.android.data.Application; +import com.xabber.android.data.account.AccountItem; +import com.xabber.android.data.account.AccountManager; +import com.xabber.android.data.account.listeners.OnAccountChangedListener; +import com.xabber.android.data.entity.AccountJid; +import com.xabber.android.data.intent.AccountIntentBuilder; +import com.xabber.android.ui.color.BarPainter; + +import java.util.Collection; + +public class AccountPushActivity extends ManagedActivity implements OnAccountChangedListener { + + private Toolbar toolbar; + private BarPainter barPainter; + private RelativeLayout rlPushSwitch; + private Switch switchPush; + private TextView tvPushState; + + private AccountItem accountItem; + + public static Intent createIntent(Context context, AccountJid account) { + return new AccountIntentBuilder(context, AccountPushActivity.class).setAccount(account).build(); + } + + private static AccountJid getAccount(Intent intent) { + return AccountIntentBuilder.getAccount(intent); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_account_push_notifications); + + final Intent intent = getIntent(); + + AccountJid account = getAccount(intent); + if (account == null) { + finish(); + return; + } + + accountItem = AccountManager.getInstance().getAccount(account); + if (accountItem == null) { + Application.getInstance().onError(R.string.NO_SUCH_ACCOUNT); + finish(); + return; + } + + toolbar = (Toolbar) findViewById(R.id.toolbar_default); + toolbar.setNavigationIcon(R.drawable.ic_arrow_left_white_24dp); + toolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + toolbar.setTitle(R.string.account_push); + + barPainter = new BarPainter(this, toolbar); + barPainter.updateWithAccountName(account); + + switchPush = findViewById(R.id.switchPush); + rlPushSwitch = findViewById(R.id.rlPushSwitch); + tvPushState = findViewById(R.id.tvPushState); + + rlPushSwitch.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + AccountManager.getInstance().setPushEnabled(accountItem, !switchPush.isChecked()); + updateSwitchButton(); + } + }); + } + + @Override + protected void onResume() { + super.onResume(); + Application.getInstance().addUIListener(OnAccountChangedListener.class, this); + checkAccount(); + updateSwitchButton(); + updateTitle(); + } + + @Override + protected void onPause() { + super.onPause(); + Application.getInstance().removeUIListener(OnAccountChangedListener.class, this); + } + + @Override + public void onAccountsChanged(Collection accounts) { + updateSwitchButton(); + } + + private void updateSwitchButton() { + boolean enabled = accountItem.getConnection().isConnected(); + rlPushSwitch.setEnabled(enabled); + switchPush.setEnabled(enabled); + + switchPush.setChecked(accountItem.isPushEnabled()); + tvPushState.setText(accountItem.isPushWasEnabled() + ? R.string.account_push_state_enabled : R.string.account_push_state_disabled); + } + + private void checkAccount() { + if (AccountManager.getInstance().getAccount(accountItem.getAccount()) == null) { + // in case if account was removed + finish(); + return; + } + } + + private void updateTitle() { + barPainter.updateWithAccountName(accountItem.getAccount()); + } +} diff --git a/xabber/src/main/java/com/xabber/android/ui/activity/ContactListActivity.java b/xabber/src/main/java/com/xabber/android/ui/activity/ContactListActivity.java index f3e5ed6700..829077cfb4 100644 --- a/xabber/src/main/java/com/xabber/android/ui/activity/ContactListActivity.java +++ b/xabber/src/main/java/com/xabber/android/ui/activity/ContactListActivity.java @@ -329,7 +329,7 @@ private void openChat(BaseEntity entity, String text) { protected void onResume() { super.onResume(); - if (!AccountManager.getInstance().hasAccounts() && XabberAccountManager.getInstance().getAccount() == null) { + if (!AccountManager.getInstance().checkAccounts() && XabberAccountManager.getInstance().getAccount() == null) { startActivity(TutorialActivity.createIntent(this)); finish(); return; diff --git a/xabber/src/main/java/com/xabber/android/ui/activity/PushLogActivity.java b/xabber/src/main/java/com/xabber/android/ui/activity/PushLogActivity.java new file mode 100644 index 0000000000..7d041e8274 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/ui/activity/PushLogActivity.java @@ -0,0 +1,67 @@ +package com.xabber.android.ui.activity; + +import android.os.Bundle; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.view.View; +import android.widget.ProgressBar; + +import com.xabber.android.R; +import com.xabber.android.data.push.PushManager; +import com.xabber.android.ui.adapter.ServerInfoAdapter; +import com.xabber.android.ui.color.BarPainter; +import com.xabber.android.ui.helper.ToolbarHelper; + +import java.util.List; + +public class PushLogActivity extends ManagedActivity implements Toolbar.OnMenuItemClickListener { + + private ServerInfoAdapter serverInfoAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_server_info); + + Toolbar toolbar = ToolbarHelper.setUpDefaultToolbar(this, getString(R.string.push_log_title)); + BarPainter barPainter = new BarPainter(this, toolbar); + barPainter.setDefaultColor(); + + toolbar.inflateMenu(R.menu.toolbar_log); + toolbar.setOnMenuItemClickListener(this); + + RecyclerView recyclerView = findViewById(R.id.server_info_recycler_view); + serverInfoAdapter = new ServerInfoAdapter(); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + recyclerView.setAdapter(serverInfoAdapter); + + ProgressBar progressBar = findViewById(R.id.server_info_progress_bar); + progressBar.setVisibility(View.GONE); + } + + @Override + protected void onResume() { + super.onResume(); + updateList(); + } + + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + if (menuItem.getItemId() == R.id.action_clear_log) { + PushManager.clearPushLog(); + updateList(); + return true; + } + return false; + } + + private void updateList() { + List log = PushManager.getPushLogs(); + if (log != null) { + serverInfoAdapter.setServerInfoList(log); + serverInfoAdapter.notifyDataSetChanged(); + } + } +} diff --git a/xabber/src/main/java/com/xabber/android/ui/activity/ServerInfoActivity.java b/xabber/src/main/java/com/xabber/android/ui/activity/ServerInfoActivity.java index 66e391d54a..45bc65f96e 100644 --- a/xabber/src/main/java/com/xabber/android/ui/activity/ServerInfoActivity.java +++ b/xabber/src/main/java/com/xabber/android/ui/activity/ServerInfoActivity.java @@ -18,6 +18,7 @@ import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager; import com.xabber.android.data.intent.AccountIntentBuilder; import com.xabber.android.data.log.LogManager; +import com.xabber.android.data.push.PushManager; import com.xabber.android.ui.adapter.ServerInfoAdapter; import com.xabber.android.ui.color.BarPainter; @@ -34,7 +35,6 @@ import org.jivesoftware.smackx.muc.MultiUserChatManager; import org.jivesoftware.smackx.muclight.MultiUserChatLightManager; import org.jivesoftware.smackx.pep.PEPManager; -import org.jivesoftware.smackx.push_notifications.PushNotificationsManager; import org.jxmpp.jid.DomainBareJid; import java.util.ArrayList; @@ -152,7 +152,7 @@ List getServerInfo(ServiceDiscoveryManager serviceDiscoveryManager) { boolean carbons = org.jivesoftware.smackx.carbons.CarbonManager.getInstanceFor(connection).isSupportedByServer(); boolean mam = MamManager.getInstanceFor(connection).isSupportedByServer(); boolean csi = ClientStateIndicationManager.isSupported(connection); - boolean push = PushNotificationsManager.getInstanceFor(connection).isSupportedByServer(); + boolean push = PushManager.getInstance().isSupport(connection); boolean fileUpload = HttpFileUploadManager.getInstance().isFileUploadSupported(accountItem.getAccount()); boolean mucLight = !MultiUserChatLightManager.getInstanceFor(connection).getLocalServices().isEmpty(); boolean bookmarks = BookmarksManager.getInstance().isSupported(accountItem.getAccount()); diff --git a/xabber/src/main/java/com/xabber/android/ui/adapter/AccountActionButtonsAdapter.java b/xabber/src/main/java/com/xabber/android/ui/adapter/AccountActionButtonsAdapter.java deleted file mode 100644 index 53ec4d14ee..0000000000 --- a/xabber/src/main/java/com/xabber/android/ui/adapter/AccountActionButtonsAdapter.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.xabber.android.ui.adapter; - -import android.app.Activity; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.LinearLayout; - -import com.melnykov.fab.FloatingActionButton; -import com.xabber.android.R; -import com.xabber.android.data.account.AccountItem; -import com.xabber.android.data.account.AccountManager; -import com.xabber.android.data.account.StatusMode; -import com.xabber.android.data.entity.AccountJid; -import com.xabber.android.data.extension.avatar.AvatarManager; -import com.xabber.android.data.xaccount.XMPPAccountSettings; -import com.xabber.android.data.xaccount.XabberAccountManager; -import com.xabber.android.ui.color.AccountPainter; -import com.xabber.android.ui.color.ColorManager; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import de.hdodenhof.circleimageview.CircleImageView; - - -public class AccountActionButtonsAdapter implements UpdatableAdapter { - - private final Activity activity; - - /** - * Listener for click on elements. - */ - private final View.OnClickListener onClickListener; - - /** - * Layout to be populated. - */ - private final LinearLayout linearLayout; - - /** - * List of accounts. - */ - private final ArrayList accounts; - - public AccountActionButtonsAdapter(Activity activity, - View.OnClickListener onClickListener, LinearLayout linearLayout) { - super(); - this.activity = activity; - this.onClickListener = onClickListener; - this.linearLayout = linearLayout; - accounts = new ArrayList<>(); - } - - /** - * Rebuild list of accounts. - *

- * Call it on account creation, deletion, enable or disable. - */ - public void rebuild() { - accounts.clear(); - accounts.addAll(AccountManager.getInstance().getEnabledAccounts()); - - Collections.sort(accounts); - final int size = accounts.size(); - final LayoutInflater inflater = (LayoutInflater) activity - .getSystemService(Activity.LAYOUT_INFLATER_SERVICE); - - while (linearLayout.getChildCount() < size) { - View view = inflater.inflate(R.layout.account_action_button, linearLayout, false); - view.setOnClickListener(onClickListener); - linearLayout.addView(view); - } - - while (linearLayout.getChildCount() > size) { - linearLayout.removeViewAt(size); - } - onChange(); - } - - @Override - public void onChange() { - Collections.sort(accounts); - - for (int index = 0; index < accounts.size(); index++) { - View view = linearLayout.getChildAt(index); - - final CircleImageView circleImageView = (CircleImageView) view.findViewById(R.id.account_avatar); - final AccountJid account = accounts.get(index); - circleImageView.setImageDrawable(AvatarManager.getInstance().getAccountAvatar(account)); - - FloatingActionButton backgroundActionButton = (FloatingActionButton) view.findViewById(R.id.fab); - - final AccountPainter accountPainter = ColorManager.getInstance().getAccountPainter(); - backgroundActionButton.setColorNormal(accountPainter.getAccountMainColor(account)); - backgroundActionButton.setColorPressed(accountPainter.getAccountDarkColor(account)); - backgroundActionButton.setColorRipple(accountPainter.getAccountRippleColor(account)); - - AccountJid selectedAccount = AccountManager.getInstance().getSelectedAccount(); - - int shadowVisibility; - - if (selectedAccount == null) { - shadowVisibility = View.GONE; - } else { - shadowVisibility = View.VISIBLE; - if (selectedAccount.equals(account)) { - shadowVisibility = View.GONE; - } - } - - view.findViewById(R.id.account_unselected_shadow).setVisibility(shadowVisibility); - - int offlineShadowVisibility; - AccountItem accountItem = AccountManager.getInstance().getAccount(account); - StatusMode statusMode = null; - if (accountItem != null) { - statusMode = accountItem.getDisplayStatusMode(); - } - if (statusMode != null && (statusMode == StatusMode.connection || statusMode == StatusMode.unavailable)) { - offlineShadowVisibility = View.VISIBLE; - } else { - offlineShadowVisibility = View.GONE; - } - view.findViewById(R.id.account_offline_shadow).setVisibility(offlineShadowVisibility); - - } - } - - public int getCount() { - return accounts.size(); - } - - public Object getItem(int position) { - return accounts.get(position); - } - - public AccountJid getItemForView(View view) { - for (int index = 0; index < linearLayout.getChildCount(); index++) { - if (view == linearLayout.getChildAt(index)) { - return accounts.get(index); - } - } - return null; - } -} diff --git a/xabber/src/main/java/com/xabber/android/ui/adapter/AccountListPreferenceAdapter.java b/xabber/src/main/java/com/xabber/android/ui/adapter/AccountListPreferenceAdapter.java index 3b34b15a2a..2448849691 100644 --- a/xabber/src/main/java/com/xabber/android/ui/adapter/AccountListPreferenceAdapter.java +++ b/xabber/src/main/java/com/xabber/android/ui/adapter/AccountListPreferenceAdapter.java @@ -23,6 +23,7 @@ import com.xabber.android.R; import com.xabber.android.data.account.AccountItem; import com.xabber.android.data.account.AccountManager; +import com.xabber.android.data.connection.ConnectionState; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.extension.avatar.AvatarManager; import com.xabber.android.data.log.LogManager; @@ -114,6 +115,12 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { accountHolder.status.setText(accountItem.getState().getStringId()); + // push state + boolean pushEnabled = accountItem.getState().equals(ConnectionState.connected) + && accountItem.isPushWasEnabled(); + accountHolder.tvAccountPushStatus.setVisibility(pushEnabled ? View.VISIBLE : View.GONE); + if (pushEnabled) accountHolder.tvAccountPushStatus.setText(R.string.account_push_state_enabled); + accountHolder.enabledSwitch.setChecked(accountItem.isEnabled()); } @@ -129,6 +136,7 @@ private class AccountViewHolder extends RecyclerView.ViewHolder implements View. TextView name; TextView status; SwitchCompat enabledSwitch; + TextView tvAccountPushStatus; AccountViewHolder(View itemView) { @@ -138,6 +146,7 @@ private class AccountViewHolder extends RecyclerView.ViewHolder implements View. name = (TextView) itemView.findViewById(R.id.item_account_name); status = (TextView) itemView.findViewById(R.id.item_account_status); enabledSwitch = (SwitchCompat) itemView.findViewById(R.id.item_account_switch); + tvAccountPushStatus = itemView.findViewById(R.id.tvAccountPushStatus); // I used on click listener instead of on checked change listener to avoid callback in onBindViewHolder enabledSwitch.setOnClickListener(this); diff --git a/xabber/src/main/java/com/xabber/android/ui/adapter/BlockedListAdapter.java b/xabber/src/main/java/com/xabber/android/ui/adapter/BlockedListAdapter.java index 2559fc9668..fe0fe614c7 100644 --- a/xabber/src/main/java/com/xabber/android/ui/adapter/BlockedListAdapter.java +++ b/xabber/src/main/java/com/xabber/android/ui/adapter/BlockedListAdapter.java @@ -57,7 +57,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { final AbstractContact rosterContact = RosterManager.getInstance().getBestContact(account, contact); if (viewHolder.avatar != null) { - viewHolder.avatar.setImageDrawable(rosterContact.getAvatarForContactList()); + viewHolder.avatar.setImageDrawable(rosterContact.getAvatar()); } viewHolder.name.setText(rosterContact.getName()); @@ -74,7 +74,7 @@ public int getItemCount() { @Override public void onChange() { blockedContacts.clear(); - final Collection blockedContacts = BlockingManager.getInstance().getBlockedContacts(account); + final Collection blockedContacts = BlockingManager.getInstance().getCachedBlockedContacts(account); if (blockedContacts != null) { this.blockedContacts.addAll(blockedContacts); } diff --git a/xabber/src/main/java/com/xabber/android/ui/adapter/ChatViewerAdapter.java b/xabber/src/main/java/com/xabber/android/ui/adapter/ChatViewerAdapter.java index b9a88f5b23..15d25e1558 100644 --- a/xabber/src/main/java/com/xabber/android/ui/adapter/ChatViewerAdapter.java +++ b/xabber/src/main/java/com/xabber/android/ui/adapter/ChatViewerAdapter.java @@ -61,7 +61,10 @@ public void selectChat(@NonNull AccountJid accountJid, @NonNull UserJid userJid) } private void setChat(@NonNull AccountJid accountJid, @NonNull UserJid userJid) { - itemCount = 3; + // Crowdfunding chat have only 2 page, other chat types have 3 page + if (CrowdfundingChat.USER.equals(userJid.getBareJid().toString())) + itemCount = 2; + else itemCount = 3; this.accountJid = accountJid; this.userJid = userJid; } diff --git a/xabber/src/main/java/com/xabber/android/ui/adapter/accountoptions/AccountOption.java b/xabber/src/main/java/com/xabber/android/ui/adapter/accountoptions/AccountOption.java index ee3b865123..993de12b8c 100644 --- a/xabber/src/main/java/com/xabber/android/ui/adapter/accountoptions/AccountOption.java +++ b/xabber/src/main/java/com/xabber/android/ui/adapter/accountoptions/AccountOption.java @@ -3,11 +3,13 @@ import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; +import com.xabber.android.BuildConfig; import com.xabber.android.R; public enum AccountOption { CONNECTION_SETTINGS(R.drawable.ic_settings_grey600_24dp, R.string.account_connection_settings), SYNCHRONIZATION(R.drawable.ic_cloud_sync, R.string.account_sync), + PUSH_NOTIFICATIONS(R.drawable.ic_sync_done, R.string.account_push), COLOR(R.drawable.ic_color_lens_grey600_24dp, R.string.account_color), BLOCK_LIST(R.drawable.ic_block_grey600_24dp, R.string.blocked_contacts), SERVER_INFO(R.drawable.ic_info_grey600_24dp, R.string.account_server_info), @@ -42,4 +44,20 @@ int getTitleId() { public String getDescription() { return description; } + + public static AccountOption[] getValues() { + if (BuildConfig.FLAVOR.equals("dev")) { + return AccountOption.values(); + } else { + int i = 0; + AccountOption[] values = new AccountOption[AccountOption.values().length - 1]; + for (AccountOption option : AccountOption.values()) { + if (option != AccountOption.PUSH_NOTIFICATIONS) { + values[i] = option; + i++; + } + } + return values; + } + } } diff --git a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/CrowdfundingChatAdapter.java b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/CrowdfundingChatAdapter.java index 618c4ae752..c70b984c58 100644 --- a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/CrowdfundingChatAdapter.java +++ b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/CrowdfundingChatAdapter.java @@ -7,7 +7,6 @@ import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.text.Html; -import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -22,10 +21,10 @@ import com.bumptech.glide.request.target.SimpleTarget; import com.xabber.android.R; import com.xabber.android.data.SettingsManager; -import com.xabber.android.data.database.messagerealm.MessageItem; import com.xabber.android.data.database.realm.CrowdfundingMessage; import com.xabber.android.data.extension.file.FileManager; import com.xabber.android.ui.color.ColorManager; +import com.xabber.android.ui.widget.CorrectlyMeasuringTextView; import com.xabber.android.utils.StringUtils; import com.xabber.android.utils.Utils; @@ -66,9 +65,9 @@ public CrowdfundingMessage getMessage(int position) { public CrowdMessageVH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { if (viewType == VIEW_TYPE_MESSAGE_NOFLEX) return new CrowdMessageVH(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_message_incoming_noflex, parent, false)); + .inflate(R.layout.item_message_incoming_noflex_crowdfunding, parent, false)); else return new CrowdMessageVH(LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_message_incoming, parent, false)); + .inflate(R.layout.item_message_incoming_crowdfunding, parent, false)); } @Override @@ -92,10 +91,12 @@ public void onBindViewHolder(@NonNull CrowdMessageVH holder, int i) { // text String text = message.getMessageForCurrentLocale(); if (text == null) text = ""; - holder.messageText.setText(Html.fromHtml(text)); + // Added .concat("‍") + // to avoid click by empty space after ClickableSpan + holder.messageText.setText(Html.fromHtml(text.concat("‍"))); // to avoid bug - https://issuetracker.google.com/issues/36907309 holder.messageText.setAutoLinkMask(0); - holder.messageText.setMovementMethod(LinkMovementMethod.getInstance()); + holder.messageText.setMovementMethod(CorrectlyMeasuringTextView.LocalLinkMovementMethod.getInstance()); // text or image if (FileManager.isImageUrl(text)) { @@ -142,7 +143,7 @@ public void onResourceReady(GlideDrawable resource, GlideAnimation attachments) { imageGridContainer.addView(imageGridView); imageGridContainer.setVisibility(View.VISIBLE); - messageText.setVisibility(View.GONE); + //messageText.setVisibility(View.GONE); } } @@ -140,7 +140,7 @@ private void setUpFile(RealmList attachments, Context context) { rvFileList.setLayoutManager(layoutManager); FilesAdapter adapter = new FilesAdapter(fileAttachments, this); rvFileList.setAdapter(adapter); - messageText.setVisibility(View.GONE); + //messageText.setVisibility(View.GONE); fileLayout.setVisibility(View.VISIBLE); } } @@ -155,7 +155,7 @@ private void setUpImage(String imagePath, String imageUrl, final String uniqueId if (result) { messageImage.setVisibility(View.VISIBLE); - messageText.setVisibility(View.GONE); + //messageText.setVisibility(View.GONE); } else { final Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread(); realm.executeTransactionAsync(new Realm.Transaction() { @@ -181,7 +181,7 @@ public void execute(Realm realm) { @Override public boolean onException(Exception e, String model, Target target, boolean isFirstResource) { messageImage.setVisibility(View.GONE); - messageText.setVisibility(View.VISIBLE); + //messageText.setVisibility(View.VISIBLE); return true; } @@ -193,7 +193,7 @@ public boolean onResourceReady(GlideDrawable resource, String model, Target").concat("‍"), + null, new ClickTagHandler(extraData.getContext(), + extraData.getMentionColor())), TextView.BufferType.SPANNABLE); + else messageText.setText(messageItem.getText().concat(String.valueOf(Character.MIN_VALUE))); if (OTRManager.getInstance().isEncrypted(messageItem.getText())) { if (extraData.isShowOriginalOTR()) messageText.setVisibility(View.VISIBLE); @@ -101,6 +113,7 @@ public void bind(MessageItem messageItem, MessagesAdapter.MessageExtraData extra messageText.setVisibility(View.VISIBLE); messageNotDecrypted.setVisibility(View.GONE); } + messageText.setMovementMethod(CorrectlyMeasuringTextView.LocalLinkMovementMethod.getInstance()); String time = StringUtils.getTimeText(new Date(messageItem.getTimestamp())); @@ -146,18 +159,22 @@ public void onGlobalLayout() { } protected void setupForwarded(MessageItem messageItem, MessagesAdapter.MessageExtraData extraData) { - RealmResults forwardedMessages = - MessageDatabaseManager.getInstance().getRealmUiThread().where(MessageItem.class) - .in(MessageItem.Fields.UNIQUE_ID, messageItem.getForwardedIdsAsArray()).findAll(); - - if (forwardedMessages.size() > 0) { - RecyclerView recyclerView = forwardLayout.findViewById(R.id.recyclerView); - ForwardedAdapter adapter = new ForwardedAdapter(forwardedMessages, extraData); - recyclerView.setLayoutManager(new LinearLayoutManager(extraData.getContext())); - recyclerView.setAdapter(adapter); - forwardLayout.setBackgroundColor(ColorManager.getColorWithAlpha(R.color.forwarded_background_color, 0.2f)); - forwardLeftBorder.setBackgroundColor(extraData.getAccountMainColor()); - forwardLayout.setVisibility(View.VISIBLE); + String[] forwardedIDs = messageItem.getForwardedIdsAsArray(); + if (!Arrays.asList(forwardedIDs).contains(null)) { + RealmResults forwardedMessages = + MessageDatabaseManager.getInstance().getRealmUiThread().where(MessageItem.class) + .in(MessageItem.Fields.UNIQUE_ID, forwardedIDs) + .findAllSorted(MessageItem.Fields.TIMESTAMP, Sort.ASCENDING); + + if (forwardedMessages.size() > 0) { + RecyclerView recyclerView = forwardLayout.findViewById(R.id.recyclerView); + ForwardedAdapter adapter = new ForwardedAdapter(forwardedMessages, extraData); + recyclerView.setLayoutManager(new LinearLayoutManager(extraData.getContext())); + recyclerView.setAdapter(adapter); + forwardLayout.setBackgroundColor(ColorManager.getColorWithAlpha(R.color.forwarded_background_color, 0.2f)); + forwardLeftBorder.setBackgroundColor(extraData.getAccountMainColor()); + forwardLayout.setVisibility(View.VISIBLE); + } } } diff --git a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/MessagesAdapter.java b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/MessagesAdapter.java index 19c875d5ce..ae1e1015be 100644 --- a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/MessagesAdapter.java +++ b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/MessagesAdapter.java @@ -53,12 +53,14 @@ public class MessagesAdapter extends RealmRecyclerViewAdapter checkedItemIds = new ArrayList<>(); public interface Listener { - void onMessageNumberChanged(int prevItemCount); void onMessagesUpdated(); void onChangeCheckedItems(int checkedItems); + int getLastVisiblePosition(); + void scrollTo(int position); } public interface AnchorHolder { @@ -94,8 +97,10 @@ public MessagesAdapter( user = chat.getUser(); userName = RosterManager.getInstance().getName(account, user); prevItemCount = getItemCount(); + prevFirstItemId = getFirstMessageId(); accountMainColor = ColorManager.getInstance().getAccountPainter().getAccountMainColor(account); colorStateList = ColorManager.getInstance().getChatIncomingBalloonColorsStateList(account); + mentionColor = ColorManager.getInstance().getAccountPainter().getAccountIndicatorBackColor(account); isMUC = MUCManager.getInstance().hasRoom(account, user.getJid().asEntityBareJidIfPossible()); if (isMUC) mucNickname = MUCManager.getInstance().getNickname(account, user.getJid().asEntityBareJidIfPossible()); @@ -108,6 +113,12 @@ public int getItemCount() { else return 0; } + private String getFirstMessageId() { + if (realmResults.isValid() && realmResults.isLoaded() && realmResults.size() > 0) + return realmResults.first().getUniqueId(); + else return null; + } + @Override public int getItemViewType(int position) { MessageItem messageItem = getMessageItem(position); @@ -202,8 +213,8 @@ public void onBindViewHolder(final BasicMessageVH holder, int position) { } else needDate = true; MessageExtraData extraData = new MessageExtraData(fileListener, fwdListener, anchorHolder, - context, userName, colorStateList, accountMainColor, isMUC, showOriginalOTR, unread, - checked, needTail, needDate); + context, userName, colorStateList, accountMainColor, mentionColor, isMUC, + showOriginalOTR, unread, checked, needTail, needDate); switch (viewType) { case VIEW_TYPE_ACTION_MESSAGE: @@ -230,13 +241,20 @@ public void onBindViewHolder(final BasicMessageVH holder, int position) { @Override public void onChange() { + int lastPosition = listener.getLastVisiblePosition(); + String firstMessageId = getFirstMessageId(); notifyDataSetChanged(); listener.onMessagesUpdated(); int itemCount = getItemCount(); if (prevItemCount != itemCount) { - listener.onMessageNumberChanged(prevItemCount); + if (firstMessageId != null && !firstMessageId.equals(prevFirstItemId)) + listener.scrollTo(lastPosition + (itemCount - prevItemCount)); + else if (lastPosition == prevItemCount - 1) + listener.scrollTo(itemCount - 1); + prevItemCount = itemCount; + prevFirstItemId = firstMessageId; } } @@ -358,6 +376,7 @@ public static class MessageExtraData { private String username; private ColorStateList colorStateList; private int accountMainColor; + private int mentionColor; private boolean isMuc; private boolean showOriginalOTR; @@ -370,7 +389,7 @@ public MessageExtraData(FileMessageVH.FileListener listener, ForwardedAdapter.ForwardListener fwdListener, AnchorHolder anchorHolder, Context context, String username, ColorStateList colorStateList, - int accountMainColor, boolean isMuc, boolean showOriginalOTR, + int accountMainColor, int mentionColor, boolean isMuc, boolean showOriginalOTR, boolean unread, boolean checked, boolean needTail, boolean needDate) { this.listener = listener; this.fwdListener = fwdListener; @@ -379,6 +398,7 @@ public MessageExtraData(FileMessageVH.FileListener listener, this.username = username; this.colorStateList = colorStateList; this.accountMainColor = accountMainColor; + this.mentionColor = mentionColor; this.isMuc = isMuc; this.showOriginalOTR = showOriginalOTR; this.unread = unread; @@ -415,6 +435,10 @@ public int getAccountMainColor() { return accountMainColor; } + public int getMentionColor() { + return mentionColor; + } + public boolean isMuc() { return isMuc; } diff --git a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/OutgoingMessageVH.java b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/OutgoingMessageVH.java index 12dcdde180..d0d5f6844d 100644 --- a/xabber/src/main/java/com/xabber/android/ui/adapter/chat/OutgoingMessageVH.java +++ b/xabber/src/main/java/com/xabber/android/ui/adapter/chat/OutgoingMessageVH.java @@ -105,17 +105,16 @@ private void setStatusIcon(MessageItem messageItem) { if (isFileUploadInProgress) progressBar.setVisibility(View.VISIBLE); - int messageIcon = 0; + int messageIcon = R.drawable.ic_message_not_sent_14dp; - if (messageItem.isError()) { - messageIcon = R.drawable.ic_message_has_error_14dp; - } else if (!isFileUploadInProgress && !messageItem.isSent() - && System.currentTimeMillis() - messageItem.getTimestamp() > 1000) { + if (!isFileUploadInProgress && !messageItem.isSent()) { messageIcon = R.drawable.ic_message_not_sent_14dp; } else if (messageItem.isDisplayed() || messageItem.isReceivedFromMessageArchive()) { messageIcon = R.drawable.ic_message_displayed; } else if (messageItem.isDelivered() || messageItem.isForwarded()) { messageIcon = R.drawable.ic_message_delivered_14dp; + } else if (messageItem.isError()) { + messageIcon = R.drawable.ic_message_has_error_14dp; } else if (messageItem.isAcknowledged()) { messageIcon = R.drawable.ic_message_acknowledged_14dp; } diff --git a/xabber/src/main/java/com/xabber/android/ui/dialog/ContactDeleteDialogFragment.java b/xabber/src/main/java/com/xabber/android/ui/dialog/ContactDeleteDialogFragment.java index ee3f054d16..f656c98628 100644 --- a/xabber/src/main/java/com/xabber/android/ui/dialog/ContactDeleteDialogFragment.java +++ b/xabber/src/main/java/com/xabber/android/ui/dialog/ContactDeleteDialogFragment.java @@ -65,8 +65,9 @@ public void onClick(DialogInterface dialog, int which) { // delete chat AbstractChat chat = MessageManager.getInstance().getChat(account, user); - if (chat != null) - MessageManager.getInstance().removeChat(chat); + if (chat != null) { + chat.setArchived(true, true); + } // remove roster contact RosterManager.getInstance().removeContact(account, user); diff --git a/xabber/src/main/java/com/xabber/android/ui/fragment/AccountAddFragment.java b/xabber/src/main/java/com/xabber/android/ui/fragment/AccountAddFragment.java index 796ec08880..3322b697b4 100644 --- a/xabber/src/main/java/com/xabber/android/ui/fragment/AccountAddFragment.java +++ b/xabber/src/main/java/com/xabber/android/ui/fragment/AccountAddFragment.java @@ -97,7 +97,7 @@ public void addAccount() { AccountJid account; try { account = AccountManager.getInstance().addAccount( - userView.getText().toString().replace(" ", ""), + userView.getText().toString().trim(), passwordView.getText().toString(), "", false, diff --git a/xabber/src/main/java/com/xabber/android/ui/fragment/ChatFragment.java b/xabber/src/main/java/com/xabber/android/ui/fragment/ChatFragment.java index 27f3ed01e8..e865236bfd 100644 --- a/xabber/src/main/java/com/xabber/android/ui/fragment/ChatFragment.java +++ b/xabber/src/main/java/com/xabber/android/ui/fragment/ChatFragment.java @@ -13,7 +13,6 @@ import android.support.annotation.Nullable; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; -import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -44,11 +43,8 @@ import com.xabber.android.data.Application; import com.xabber.android.data.NetworkException; import com.xabber.android.data.SettingsManager; -import com.xabber.android.data.account.AccountItem; -import com.xabber.android.data.account.AccountManager; import com.xabber.android.data.account.listeners.OnAccountChangedListener; import com.xabber.android.data.database.messagerealm.MessageItem; -import com.xabber.android.data.database.messagerealm.SyncInfo; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.entity.BaseEntity; import com.xabber.android.data.entity.UserJid; @@ -59,8 +55,7 @@ import com.xabber.android.data.extension.httpfileupload.HttpFileUploadManager; import com.xabber.android.data.extension.mam.LastHistoryLoadFinishedEvent; import com.xabber.android.data.extension.mam.LastHistoryLoadStartedEvent; -import com.xabber.android.data.extension.mam.LoadHistorySettings; -import com.xabber.android.data.extension.mam.MamManager; +import com.xabber.android.data.extension.mam.NextMamManager; import com.xabber.android.data.extension.mam.PreviousHistoryLoadFinishedEvent; import com.xabber.android.data.extension.mam.PreviousHistoryLoadStartedEvent; import com.xabber.android.data.extension.muc.MUCManager; @@ -119,7 +114,6 @@ import github.ankushsachdeva.emojicon.EmojiconsPopup; import github.ankushsachdeva.emojicon.emoji.Emojicon; import io.realm.RealmResults; -import io.realm.Sort; public class ChatFragment extends FileInteractionFragment implements PopupMenu.OnMenuItemClickListener, View.OnClickListener, Toolbar.OnMenuItemClickListener, MessageVH.MessageClickListener, @@ -148,7 +142,6 @@ public class ChatFragment extends FileInteractionFragment implements PopupMenu.O private RecyclerView realmRecyclerView; private MessagesAdapter chatMessageAdapter; private LinearLayoutManager layoutManager; - private SwipeRefreshLayout swipeContainer; private View placeholder; private LinearLayout inputLayout; private ViewStub stubJoin; @@ -175,11 +168,8 @@ public class ChatFragment extends FileInteractionFragment implements PopupMenu.O private Timer stopTypingTimer = new Timer(); - private boolean isRemoteHistoryRequested = false; - private int firstRemoteSyncedItemPosition = RecyclerView.NO_POSITION; - private RealmResults syncInfoResults; + private boolean historyIsLoading = false; private RealmResults messageItems; - private boolean toBeScrolled; private List> menuItems = null; @@ -333,14 +323,7 @@ public void onClick(View v) { public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); - if (dy < 0) { - loadHistoryIfNeeded(); - } - - if (dy >= 0) { - toBeScrolled = false; - } - + if (dy < 0) loadHistoryIfNeed(); showScrollDownButtonIfNeed(); /** Necessary for @@ -350,21 +333,6 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { } }); - swipeContainer = (SwipeRefreshLayout) view.findViewById(R.id.swipeContainer); - swipeContainer.setColorSchemeColors(ColorManager.getInstance().getAccountPainter().getAccountMainColor(account)); - swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - swipeContainer.setRefreshing(false); - AbstractChat chat = getChat(); - if (chat != null) { - if (chat.isRemotePreviousHistoryCompletelyLoaded()) - Toast.makeText(getActivity(), R.string.toast_no_history, Toast.LENGTH_SHORT).show(); - else requestRemoteHistoryLoad(); - } - } - }); - stubNotify = (ViewStub) view.findViewById(R.id.stubNotify); stubJoin = (ViewStub) view.findViewById(R.id.stubJoin); @@ -397,7 +365,6 @@ public void setChat(AccountJid accountJid, UserJid userJid) { if (abstractChat != null) { messageItems = abstractChat.getMessages(); - syncInfoResults = abstractChat.getSyncInfo(); } chatMessageAdapter = new MessagesAdapter(getActivity(), messageItems, abstractChat, @@ -414,17 +381,7 @@ public void setChat(AccountJid accountJid, UserJid userJid) { public void onStart() { super.onStart(); EventBus.getDefault().register(this); - - AccountItem accountItem = AccountManager.getInstance().getAccount(this.account); - if (accountItem != null) { - LoadHistorySettings loadHistorySettings = accountItem.getLoadHistorySettings(); - - if (loadHistorySettings == LoadHistorySettings.all || loadHistorySettings == LoadHistorySettings.current) { - if (!isRemoteHistoryRequested) { - MamManager.getInstance().requestLastHistoryByUser(getChat()); - } - } - } + NextMamManager.getInstance().onChatOpen(getChat()); } @Override @@ -639,54 +596,13 @@ public void onClick(View v) { }); } - private void loadHistoryIfNeeded() { - AccountItem accountItem = AccountManager.getInstance().getAccount(this.account); - if (accountItem == null) { - return; - } - - LoadHistorySettings loadHistorySettings = accountItem.getLoadHistorySettings(); - - if (loadHistorySettings != LoadHistorySettings.current - && loadHistorySettings != LoadHistorySettings.all) { - return; - } - - if (isRemoteHistoryRequested) { - return; - } - - int visibleItemCount = layoutManager.getChildCount(); - - if (visibleItemCount == 0) { - return; - } - - int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition(); - - if (firstVisibleItemPosition / visibleItemCount <= 2) { - requestRemoteHistoryLoad(); - return; - } - - if (firstVisibleItemPosition < firstRemoteSyncedItemPosition) { - requestRemoteHistoryLoad(); - return; - } - - if (firstVisibleItemPosition - firstRemoteSyncedItemPosition < visibleItemCount * 2) { - requestRemoteHistoryLoad(); - return; - } - } - - private void requestRemoteHistoryLoad() { - if (!isRemoteHistoryRequested) { - AbstractChat chat = getChat(); - if (chat != null) { - MamManager.getInstance().requestPreviousHistory(chat); + private void loadHistoryIfNeed() { + if (!historyIsLoading) { + int invisibleMessagesCount = layoutManager.findFirstVisibleItemPosition(); + if (invisibleMessagesCount <= 15) { + AbstractChat chat = getChat(); + if (chat != null) NextMamManager.getInstance().onScrollInChat(chat); } - } } @@ -700,7 +616,7 @@ private AbstractChat getChat() { public void onEvent(LastHistoryLoadStartedEvent event) { if (event.getAccount().equals(account) && event.getUser().equals(user)) { lastHistoryProgressBar.setVisibility(View.VISIBLE); - isRemoteHistoryRequested = true; + historyIsLoading = true; } } @@ -708,7 +624,7 @@ public void onEvent(LastHistoryLoadStartedEvent event) { public void onEvent(LastHistoryLoadFinishedEvent event) { if (event.getAccount().equals(account) && event.getUser().equals(user)) { lastHistoryProgressBar.setVisibility(View.GONE); - isRemoteHistoryRequested = false; + historyIsLoading = false; } } @@ -717,8 +633,7 @@ public void onEvent(PreviousHistoryLoadStartedEvent event) { if (event.getAccount().equals(account) && event.getUser().equals(user)) { LogManager.i(this, "PreviousHistoryLoadStartedEvent"); previousHistoryProgressBar.setVisibility(View.VISIBLE); - isRemoteHistoryRequested = true; - swipeContainer.setRefreshing(true); + historyIsLoading = true; } } @@ -726,9 +641,8 @@ public void onEvent(PreviousHistoryLoadStartedEvent event) { public void onEvent(PreviousHistoryLoadFinishedEvent event) { if (event.getAccount().equals(account) && event.getUser().equals(user)) { LogManager.i(this, "PreviousHistoryLoadFinishedEvent"); - isRemoteHistoryRequested = false; + historyIsLoading = false; previousHistoryProgressBar.setVisibility(View.GONE); - swipeContainer.setRefreshing(false); } } @@ -736,7 +650,6 @@ public void onEvent(PreviousHistoryLoadFinishedEvent event) { public void onEvent(MessageUpdateEvent event) { if (account.equals(event.getAccount()) && user.equals(event.getUser())) { updateUnread(); - chatMessageAdapter.onChange(); } } @@ -744,7 +657,7 @@ public void onEvent(MessageUpdateEvent event) { public void onEvent(NewIncomingMessageEvent event) { if (event.getAccount().equals(account) && event.getUser().equals(user)) { listener.playIncomingAnimation(); - //playIncomingSound(); + playMessageSound(); } } @@ -847,6 +760,8 @@ public void saveInputState() { private void sendMessage() { String text = inputView.getText().toString().trim(); clearInputText(); + scrollDown(); + playMessageSound(); if (forwardIds != null && !forwardIds.isEmpty()) { sendForwardMessage(forwardIds, text); @@ -868,7 +783,6 @@ private void sendMessage() { private void sendMessage(String text) { MessageManager.getInstance().sendMessage(account, user, text); setFirstUnreadMessageId(null); - scrollDown(); } @@ -892,6 +806,7 @@ private void onScrollDownClick() { } private void scrollDown() { + realmRecyclerView.scrollToPosition(chatMessageAdapter.getItemCount() - 1); } @@ -1275,38 +1190,38 @@ public void onAccountsChanged(Collection accounts) { chatMessageAdapter.notifyDataSetChanged(); } - public void playIncomingSound() { - if (SettingsManager.eventsInChatSounds()) { - final MediaPlayer mp; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - AudioAttributes attr = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN) - .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT).build(); - mp = MediaPlayer.create(getActivity(), SettingsManager.eventsSound(), - null, attr, AudioManager.AUDIO_SESSION_ID_GENERATE); - } else { - mp = MediaPlayer.create(getActivity(), SettingsManager.eventsSound()); - mp.setAudioStreamType(AudioManager.STREAM_NOTIFICATION); - } + public void playMessageSound() { + if (!SettingsManager.eventsInChatSounds()) return; - mp.start(); - mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(MediaPlayer mediaPlayer) { - mp.release(); - } - }); + final MediaPlayer mp; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + AudioAttributes attr = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN) + .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT).build(); + mp = MediaPlayer.create(getActivity(), R.raw.message_alert, + attr, AudioManager.AUDIO_SESSION_ID_GENERATE); + } else { + mp = MediaPlayer.create(getActivity(), R.raw.message_alert); + mp.setAudioStreamType(AudioManager.STREAM_NOTIFICATION); } + + mp.start(); + mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mediaPlayer) { + mp.release(); + } + }); } @Override - public void onMessageNumberChanged(int prevItemCount) { - int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition(); + public int getLastVisiblePosition() { + return layoutManager.findLastVisibleItemPosition(); + } - if (toBeScrolled || lastVisibleItemPosition == -1 || lastVisibleItemPosition == (prevItemCount - 1)) { - toBeScrolled = true; - scrollDown(); - } + @Override + public void scrollTo(int position) { + layoutManager.scrollToPosition(position); } public void saveScrollState() { @@ -1334,32 +1249,7 @@ else if (position > 0) @Override public void onMessagesUpdated() { - updateFirstRemoteSyncedItemPosition(); - loadHistoryIfNeeded(); - } - - private void updateFirstRemoteSyncedItemPosition() { - if (!syncInfoResults.isLoaded() || !messageItems.isLoaded() || syncInfoResults.isEmpty()) { - return; - } - - SyncInfo syncInfo = syncInfoResults.first(); - - String firstMamMessageStanzaId = syncInfo.getFirstMamMessageStanzaId(); - if (firstMamMessageStanzaId == null) { - return; - } - - RealmResults allSorted = messageItems.where() - .equalTo(MessageItem.Fields.STANZA_ID, firstMamMessageStanzaId) - .findAllSorted(MessageItem.Fields.TIMESTAMP, Sort.ASCENDING); - if (allSorted.isEmpty()) { - return; - } - - String firstRemotelySyncedMessageUniqueId = allSorted.last().getUniqueId(); - - firstRemoteSyncedItemPosition = chatMessageAdapter.findMessagePosition(firstRemotelySyncedMessageUniqueId); + loadHistoryIfNeed(); } public interface ChatViewerFragmentListener { @@ -1529,7 +1419,6 @@ private void sendForwardMessage(List messages, String text) { ForwardManager.forwardMessage(messages, account, user, text); hideForwardPanel(); setFirstUnreadMessageId(null); - scrollDown(); } private void openChooserForForward(ArrayList forwardIds) { diff --git a/xabber/src/main/java/com/xabber/android/ui/fragment/ContactAddFragment.java b/xabber/src/main/java/com/xabber/android/ui/fragment/ContactAddFragment.java index 2f8a92bb52..e3b0852bb1 100644 --- a/xabber/src/main/java/com/xabber/android/ui/fragment/ContactAddFragment.java +++ b/xabber/src/main/java/com/xabber/android/ui/fragment/ContactAddFragment.java @@ -147,7 +147,8 @@ public void onItemSelected(AdapterView parent, View view, int position, long onNothingSelected(parent); setAccount(null); } else { - listenerActivity.onAccountSelected(selectedAccount); + if (listenerActivity != null) + listenerActivity.onAccountSelected(selectedAccount); if (!selectedAccount.equals(getAccount())) { setAccount(selectedAccount); @@ -167,18 +168,22 @@ public void onNothingSelected(AdapterView parent) { @Override public void addContact() { - if (getAccount() == null) { - Toast.makeText(getActivity(), getString(R.string.EMPTY_ACCOUNT), - Toast.LENGTH_LONG).show(); + final AccountJid account = (AccountJid) accountView.getSelectedItem(); + if (account == null || getAccount() == null) { + Toast.makeText(getActivity(), getString(R.string.EMPTY_ACCOUNT), Toast.LENGTH_LONG).show(); return; } String contactString = userView.getText().toString(); - contactString = contactString.replace(" ", ""); + contactString = contactString.trim(); + + if (contactString.contains(" ")) { + userView.setError(getString(R.string.INCORRECT_USER_NAME)); + return; + } if (TextUtils.isEmpty(contactString)) { - Toast.makeText(getActivity(), getString(R.string.EMPTY_USER_NAME), - Toast.LENGTH_LONG).show(); + userView.setError(getString(R.string.EMPTY_USER_NAME)); return ; } @@ -188,20 +193,12 @@ public void addContact() { user = UserJid.from(entityFullJid); } catch (XmppStringprepException | UserJid.UserJidCreateException e) { e.printStackTrace(); - Toast.makeText(getActivity(), getString(R.string.INCORRECT_USER_NAME), Toast.LENGTH_LONG).show(); - return; - } - - LogManager.i(this, "user: " + user); - - final AccountJid account = (AccountJid) accountView.getSelectedItem(); - if (account == null) { - Toast.makeText(getActivity(), getString(R.string.EMPTY_ACCOUNT), - Toast.LENGTH_LONG).show(); + userView.setError(getString(R.string.INCORRECT_USER_NAME)); return; } - listenerActivity.showProgress(true); + if (listenerActivity != null) + listenerActivity.showProgress(true); final String name = nameView.getText().toString(); final ArrayList groups = getSelected(); @@ -237,7 +234,8 @@ private void stopAddContactProcess(final boolean success) { Application.getInstance().runOnUiThread(new Runnable() { @Override public void run() { - listenerActivity.showProgress(false); + if (listenerActivity != null) + listenerActivity.showProgress(false); if (success) getActivity().finish(); } }); diff --git a/xabber/src/main/java/com/xabber/android/ui/fragment/ContactVcardViewerFragment.java b/xabber/src/main/java/com/xabber/android/ui/fragment/ContactVcardViewerFragment.java index 24db65d9dc..151fe0dcb2 100644 --- a/xabber/src/main/java/com/xabber/android/ui/fragment/ContactVcardViewerFragment.java +++ b/xabber/src/main/java/com/xabber/android/ui/fragment/ContactVcardViewerFragment.java @@ -415,8 +415,7 @@ private void fillResourceList(AccountJid account, Jid bareAddress, List re ImageView statusIcon = (ImageView) resourceView.findViewById(R.id.contact_info_right_icon); statusIcon.setVisibility(View.VISIBLE); - - statusIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_status)); + statusIcon.setImageResource(R.drawable.ic_status); statusIcon.setImageLevel(statusMode.getStatusLevel()); resourcesList.add(resourceView); diff --git a/xabber/src/main/java/com/xabber/android/ui/fragment/FileInteractionFragment.java b/xabber/src/main/java/com/xabber/android/ui/fragment/FileInteractionFragment.java index bf203aa1fa..6f57d9bdd3 100644 --- a/xabber/src/main/java/com/xabber/android/ui/fragment/FileInteractionFragment.java +++ b/xabber/src/main/java/com/xabber/android/ui/fragment/FileInteractionFragment.java @@ -280,8 +280,9 @@ public void onForwardClick(String messageId) { protected void onAttachButtonPressed() { if (!HttpFileUploadManager.getInstance().isFileUploadSupported(account)) { // show notification + String serverName = account.getFullJid().getDomain().toString(); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setMessage(R.string.error_file_upload_not_support) + builder.setMessage(getActivity().getResources().getString(R.string.error_file_upload_not_support, serverName)) .setTitle(getString(R.string.error_sending_file, "")) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override diff --git a/xabber/src/main/java/com/xabber/android/ui/fragment/ForwardedFragment.java b/xabber/src/main/java/com/xabber/android/ui/fragment/ForwardedFragment.java index eeb70063ae..7ebecb908c 100644 --- a/xabber/src/main/java/com/xabber/android/ui/fragment/ForwardedFragment.java +++ b/xabber/src/main/java/com/xabber/android/ui/fragment/ForwardedFragment.java @@ -33,6 +33,7 @@ public class ForwardedFragment extends FileInteractionFragment { private String userName; private int accountMainColor; + private int mentionColor; private ColorStateList colorStateList; private boolean isMUC; private String messageId; @@ -62,6 +63,7 @@ public void onCreate(Bundle savedInstanceState) { userName = RosterManager.getInstance().getName(account, user); accountMainColor = ColorManager.getInstance().getAccountPainter().getAccountMainColor(account); + mentionColor = ColorManager.getInstance().getAccountPainter().getAccountIndicatorBackColor(account); colorStateList = ColorManager.getInstance().getChatIncomingBalloonColorsStateList(account); isMUC = MUCManager.getInstance().hasRoom(account, user.getJid().asEntityBareJidIfPossible()); } @@ -101,8 +103,8 @@ public void onResume() { MessagesAdapter.MessageExtraData extraData = new MessagesAdapter.MessageExtraData(this, this, null, getActivity(), - userName, colorStateList, accountMainColor, isMUC, false, false, - false, false, false); + userName, colorStateList, accountMainColor, mentionColor, isMUC, false, + false, false, false, false); if (forwardedMessages.size() > 0) { ForwardedAdapter adapter = new ForwardedAdapter(forwardedMessages, extraData); diff --git a/xabber/src/main/java/com/xabber/android/ui/preferences/AccountHistorySettingsFragment.java b/xabber/src/main/java/com/xabber/android/ui/preferences/AccountHistorySettingsFragment.java index 5fe4043211..870038cb61 100644 --- a/xabber/src/main/java/com/xabber/android/ui/preferences/AccountHistorySettingsFragment.java +++ b/xabber/src/main/java/com/xabber/android/ui/preferences/AccountHistorySettingsFragment.java @@ -13,7 +13,7 @@ import com.xabber.android.data.account.listeners.OnAccountChangedListener; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.extension.mam.LoadHistorySettings; -import com.xabber.android.data.extension.mam.MamManager; +import com.xabber.android.data.extension.mam.NextMamManager; import org.jivesoftware.smackx.mam.element.MamPrefsIQ; @@ -53,7 +53,7 @@ protected void onInflate(Bundle savedInstanceState) { } private void setUpMamPreference(Preference mamPreference, @Nullable String newSummary) { - Boolean supported = MamManager.getInstance().isSupported(account); + Boolean supported = NextMamManager.getInstance().isSupported(account); if (supported == null) { mamPreference.setEnabled(false); mamPreference.setSummary(getString(R.string.account_chat_history_unknown)); diff --git a/xabber/src/main/java/com/xabber/android/ui/preferences/ConnectionSettingsFragment.java b/xabber/src/main/java/com/xabber/android/ui/preferences/ConnectionSettingsFragment.java index 249e4e99b3..ee950bb32e 100644 --- a/xabber/src/main/java/com/xabber/android/ui/preferences/ConnectionSettingsFragment.java +++ b/xabber/src/main/java/com/xabber/android/ui/preferences/ConnectionSettingsFragment.java @@ -1,23 +1,13 @@ package com.xabber.android.ui.preferences; -import android.content.SharedPreferences; import android.os.Bundle; -import android.preference.CheckBoxPreference; import android.preference.Preference; -import android.preference.PreferenceManager; import com.xabber.android.R; -import com.xabber.android.data.SettingsManager; -import com.xabber.android.data.account.AccountItem; -import com.xabber.android.data.account.AccountManager; -import com.xabber.android.data.entity.AccountJid; import com.xabber.android.ui.activity.PreferenceSummaryHelperActivity; import com.xabber.android.ui.helper.BatteryHelper; -import java.util.Collection; - -public class ConnectionSettingsFragment extends android.preference.PreferenceFragment - implements SharedPreferences.OnSharedPreferenceChangeListener { +public class ConnectionSettingsFragment extends android.preference.PreferenceFragment { private Preference batteryOptimizationPreference; @@ -29,8 +19,6 @@ public void onCreate(Bundle savedInstanceState) { PreferenceSummaryHelperActivity.updateSummary(getPreferenceScreen()); - //setDnsResolverSummary(SettingsManager.connectionDnsResolver()); - batteryOptimizationPreference = findPreference(getString(R.string.battery_optimization_disable_key)); batteryOptimizationPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override @@ -45,51 +33,9 @@ public boolean onPreferenceClick(Preference preference) { @Override public void onResume() { super.onResume(); - PreferenceManager.getDefaultSharedPreferences(getActivity()) - .registerOnSharedPreferenceChangeListener(this); - updateBatteryOptimizationPreference(); } - @Override - public void onPause() { - super.onPause(); - PreferenceManager.getDefaultSharedPreferences(getActivity()) - .unregisterOnSharedPreferenceChangeListener(this); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { -// if (key.equals(getString(R.string.connection_dns_resolver_type_key))) { -// String value = sharedPreferences.getString(key, getString(R.string.connection_dns_resolver_type_default)); -// SettingsManager.DnsResolverType dnsResolverType = SettingsManager.getDnsResolverType(value); -// setDnsResolverSummary(dnsResolverType); -// -// // reconnect all enabled account to apply and check changes -// Collection enabledAccounts = AccountManager.getInstance().getEnabledAccounts(); -// for (AccountJid accountJid : enabledAccounts) { -// AccountItem accountItem = AccountManager.getInstance().getAccount(accountJid); -// if (accountItem != null) { -// accountItem.recreateConnection(); -// } -// } -// } - } - - private void setDnsResolverSummary(SettingsManager.DnsResolverType dnsResolverType) { - Preference preference = findPreference(getString(R.string.connection_dns_resolver_type_key)); - String summary = ""; - switch (dnsResolverType) { - case dnsJavaResolver: - summary = getString(R.string.connection_dns_resolver_type_dns_java_resolver); - break; - case miniDnsResolver: - summary = getString(R.string.connection_dns_resolver_type_mini_dns_resolver); - break; - } - preference.setSummary(summary); - } - private void updateBatteryOptimizationPreference() { if (!BatteryHelper.isOptimizingBattery()) batteryOptimizationPreference.setSummary(R.string.battery_optimization_disabled); diff --git a/xabber/src/main/java/com/xabber/android/ui/preferences/DebugSettingsFragment.java b/xabber/src/main/java/com/xabber/android/ui/preferences/DebugSettingsFragment.java index 96b6327676..5beb15ef14 100644 --- a/xabber/src/main/java/com/xabber/android/ui/preferences/DebugSettingsFragment.java +++ b/xabber/src/main/java/com/xabber/android/ui/preferences/DebugSettingsFragment.java @@ -10,7 +10,7 @@ import com.xabber.android.R; import com.xabber.android.data.Application; import com.xabber.android.data.SettingsManager; -import com.xabber.android.data.extension.mam.MamManager; +import com.xabber.android.data.extension.mam.NextMamManager; import com.xabber.android.data.http.CrowdfundingManager; import com.xabber.android.data.message.AbstractChat; import com.xabber.android.data.message.MessageManager; @@ -112,7 +112,7 @@ public void run() { for (AbstractChat chat : chats) { setDownloadProgress(totalArchives, downloadedArchives); - MamManager.getInstance().requestFullChatHistory(chat); + NextMamManager.getInstance().loadFullChatHistory(chat); downloadedArchives++; } closeDownloadArchiveDialog(); diff --git a/xabber/src/main/java/com/xabber/android/ui/text/ClickSpan.java b/xabber/src/main/java/com/xabber/android/ui/text/ClickSpan.java new file mode 100644 index 0000000000..2cf5cb2593 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/ui/text/ClickSpan.java @@ -0,0 +1,49 @@ +package com.xabber.android.ui.text; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.text.TextPaint; +import android.text.style.ClickableSpan; +import android.view.View; + +public class ClickSpan extends ClickableSpan { + + public final static String TYPE_HYPERLINK = "hyperlink"; + public final static String TYPE_MENTION = "mention"; + + private final String url; + private final String type; + private final Context context; + + public ClickSpan(String url, String type, Context context) { + this.url = url; + this.type = type; + this.context = context; + } + + @Override + public void onClick(View view) { + if (url != null && context != null) { + if (TYPE_HYPERLINK.equals(type)) { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + context.startActivity(browserIntent); + } + } + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + if (TYPE_HYPERLINK.equals(type)) ds.setUnderlineText(true); + ds.setColor(ds.linkColor); + } + + public String getUrl() { + return url; + } + + public String getType() { + return type; + } +} diff --git a/xabber/src/main/java/com/xabber/android/ui/text/ClickTagHandler.java b/xabber/src/main/java/com/xabber/android/ui/text/ClickTagHandler.java new file mode 100644 index 0000000000..fccaaaf604 --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/ui/text/ClickTagHandler.java @@ -0,0 +1,118 @@ +package com.xabber.android.ui.text; + +import android.content.Context; +import android.text.Editable; +import android.text.Html; +import android.text.Spannable; +import android.text.style.BackgroundColorSpan; +import android.text.style.ForegroundColorSpan; +import android.util.Log; + +import com.xabber.android.R; + +import org.xml.sax.XMLReader; + +import java.lang.reflect.Field; + +public class ClickTagHandler implements Html.TagHandler { + + private final static String FIELD_NEW_ELEMENT = "theNewElement"; + private final static String FIELD_ATTS = "theAtts"; + private final static String FIELD_DATA = "data"; + private final static String FIELD_LENGTH = "length"; + private final static String ATTRIBUTE_URI = "uri"; + private final static String ATTRIBUTE_TYPE = "type"; + private final static String TAG = "click"; + + private Context context; + private int backgroundColor; + + public ClickTagHandler(Context context, int backgroundColor) { + this.context = context; + this.backgroundColor = backgroundColor; + } + + @Override + public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) { + if (tag.equalsIgnoreCase(TAG)) { + processTag(opening, output, xmlReader); + } + } + + private void processTag(boolean opening, Editable output, XMLReader xmlReader) { + int len = output.length(); + + if (opening) { + String uri = getAttrubute(xmlReader, ATTRIBUTE_URI); + String type = getAttrubute(xmlReader, ATTRIBUTE_TYPE); + output.setSpan(new ClickSpan(uri, type, context), len, len, Spannable.SPAN_MARK_MARK); + } else { + Object obj = getLast(output, ClickSpan.class); + int where = output.getSpanStart(obj); + String uri = null, type = null; + + if (obj instanceof ClickSpan) { + uri = ((ClickSpan)obj).getUrl(); + type = ((ClickSpan)obj).getType(); + } + + output.removeSpan(obj); + + if (where != len && uri != null && type != null) { + output.setSpan(new ClickSpan(uri, type, context), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + if (ClickSpan.TYPE_MENTION.equals(type)) { + output.setSpan(new BackgroundColorSpan(backgroundColor), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + output.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.black_text)), + where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } + } + + private Object getLast(Editable text, Class kind) { + Object[] objs = text.getSpans(0, text.length(), kind); + + if (objs.length == 0) { + return null; + } else { + for(int i = objs.length;i>0;i--) { + if(text.getSpanFlags(objs[i-1]) == Spannable.SPAN_MARK_MARK) { + return objs[i-1]; + } + } + return null; + } + } + + private String getAttrubute(XMLReader xmlReader, String attrName) { + String attribute = null; + try { + Field elementField = xmlReader.getClass().getDeclaredField(FIELD_NEW_ELEMENT); + elementField.setAccessible(true); + Object element = elementField.get(xmlReader); + + Field attsField = element.getClass().getDeclaredField(FIELD_ATTS); + attsField.setAccessible(true); + Object atts = attsField.get(element); + + Field dataField = atts.getClass().getDeclaredField(FIELD_DATA); + dataField.setAccessible(true); + String[] data = (String[]) dataField.get(atts); + + Field lengthField = atts.getClass().getDeclaredField(FIELD_LENGTH); + lengthField.setAccessible(true); + int length = (Integer) lengthField.get(atts); + + for (int i = 0; i < length; i++) { + if (attrName.equals(data[i * 5 + 1])) { + attribute = data[i * 5 + 4]; + } + } + } catch (NoSuchFieldException | IllegalAccessException | NullPointerException e) { + Log.d(ClickTagHandler.class.getSimpleName(), + "Error on getting attribute '" + attrName + "': " + e.toString()); + } + return attribute; + } +} diff --git a/xabber/src/main/java/com/xabber/android/ui/widget/CorrectlyMeasuringTextView.java b/xabber/src/main/java/com/xabber/android/ui/widget/CorrectlyMeasuringTextView.java index c43b7a1656..34fe4be946 100644 --- a/xabber/src/main/java/com/xabber/android/ui/widget/CorrectlyMeasuringTextView.java +++ b/xabber/src/main/java/com/xabber/android/ui/widget/CorrectlyMeasuringTextView.java @@ -1,11 +1,10 @@ package com.xabber.android.ui.widget; import android.content.Context; -import android.support.v7.widget.AppCompatTextView; import android.text.Layout; import android.util.AttributeSet; -public class CorrectlyMeasuringTextView extends AppCompatTextView { +public class CorrectlyMeasuringTextView extends CorrectlyTouchEventTextView { public CorrectlyMeasuringTextView(Context context) { super(context); diff --git a/xabber/src/main/java/com/xabber/android/ui/widget/CorrectlyTouchEventTextView.java b/xabber/src/main/java/com/xabber/android/ui/widget/CorrectlyTouchEventTextView.java new file mode 100644 index 0000000000..be6729485e --- /dev/null +++ b/xabber/src/main/java/com/xabber/android/ui/widget/CorrectlyTouchEventTextView.java @@ -0,0 +1,123 @@ +package com.xabber.android.ui.widget; + +import android.content.Context; +import android.support.v7.widget.AppCompatTextView; +import android.text.Layout; +import android.text.Selection; +import android.text.Spannable; +import android.text.method.LinkMovementMethod; +import android.text.method.Touch; +import android.text.style.BackgroundColorSpan; +import android.text.style.ClickableSpan; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.TextView; + +import com.xabber.android.R; + +public class CorrectlyTouchEventTextView extends AppCompatTextView { + boolean clickableSpanClicked; + + public CorrectlyTouchEventTextView(Context context) { + super(context); + } + + public CorrectlyTouchEventTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CorrectlyTouchEventTextView( + Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + clickableSpanClicked = false; + super.onTouchEvent(event); + return clickableSpanClicked; + } + + public static class LocalLinkMovementMethod extends LinkMovementMethod { + static LocalLinkMovementMethod instance; + + private boolean isUrlHighlighted; + + public static LocalLinkMovementMethod getInstance() { + if (instance == null) + instance = new LocalLinkMovementMethod(); + return instance; + } + + @Override + public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { + int action = event.getAction(); + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + x -= widget.getTotalPaddingLeft(); + y -= widget.getTotalPaddingTop(); + + x += widget.getScrollX(); + y += widget.getScrollY(); + + Layout layout = widget.getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + + ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); + + if (link.length != 0) { + if (action == MotionEvent.ACTION_UP) { + link[0].onClick(widget); + removeUrlHighlightColor(widget); + } else if (action == MotionEvent.ACTION_DOWN) { + Selection.setSelection(buffer, + buffer.getSpanStart(link[0]), + buffer.getSpanEnd(link[0])); + highlightUrl(widget, link[0], buffer); + } + + if (widget instanceof CorrectlyTouchEventTextView){ + ((CorrectlyTouchEventTextView) widget).clickableSpanClicked = true; + } + + return true; + } else { + Selection.removeSelection(buffer); + Touch.onTouchEvent(widget, buffer, event); + return false; + } + } + return Touch.onTouchEvent(widget, buffer, event); + } + + protected void highlightUrl(TextView textView, ClickableSpan clickableSpan, Spannable text) { + if (isUrlHighlighted) return; + isUrlHighlighted = true; + + int spanStart = text.getSpanStart(clickableSpan); + int spanEnd = text.getSpanEnd(clickableSpan); + BackgroundColorSpan highlightSpan; + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { + highlightSpan = new BackgroundColorSpan(textView.getHighlightColor()); + } else highlightSpan = new BackgroundColorSpan(textView.getLinkTextColors().getDefaultColor()); + text.setSpan(highlightSpan, spanStart, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + textView.setTag(R.id.clickable_span_highlight_background, highlightSpan); + Selection.setSelection(text, spanStart, spanEnd); + } + + protected void removeUrlHighlightColor(TextView textView) { + if (!isUrlHighlighted) return; + isUrlHighlighted = false; + + Spannable text = (Spannable) textView.getText(); + BackgroundColorSpan highlightSpan = (BackgroundColorSpan) textView.getTag(R.id.clickable_span_highlight_background); + text.removeSpan(highlightSpan); + Selection.removeSelection(text); + } + } +} diff --git a/xabber/src/main/java/com/xabber/android/ui/widget/ImageGridBuilder.java b/xabber/src/main/java/com/xabber/android/ui/widget/ImageGridBuilder.java index a3a8a51f1e..56b6b3daa1 100644 --- a/xabber/src/main/java/com/xabber/android/ui/widget/ImageGridBuilder.java +++ b/xabber/src/main/java/com/xabber/android/ui/widget/ImageGridBuilder.java @@ -8,11 +8,8 @@ import android.widget.TextView; import com.bumptech.glide.Glide; -import com.bumptech.glide.load.resource.drawable.GlideDrawable; -import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; -import com.bumptech.glide.request.target.Target; import com.xabber.android.R; import com.xabber.android.data.database.MessageDatabaseManager; import com.xabber.android.data.database.messagerealm.Attachment; @@ -24,6 +21,8 @@ public class ImageGridBuilder { + private static final int MAX_IMAGE_IN_GRID = 5; + public View inflateView(ViewGroup parent, int imageCount) { return LayoutInflater.from(parent.getContext()).inflate(getLayoutResource(imageCount), parent, false); } @@ -51,8 +50,8 @@ public void bindView(View view, RealmList attachments, View.OnClickL } if (tvCounter != null) { - if (attachments.size() > 6) { - tvCounter.setText("+" + (attachments.size() - 6)); + if (attachments.size() > MAX_IMAGE_IN_GRID) { + tvCounter.setText(new StringBuilder("+").append(attachments.size() - MAX_IMAGE_IN_GRID)); tvCounter.setVisibility(View.VISIBLE); } else tvCounter.setVisibility(View.GONE); } diff --git a/xabber/src/main/java/com/xabber/android/utils/StringUtils.java b/xabber/src/main/java/com/xabber/android/utils/StringUtils.java index 10d8ef41bc..ffb667f189 100644 --- a/xabber/src/main/java/com/xabber/android/utils/StringUtils.java +++ b/xabber/src/main/java/com/xabber/android/utils/StringUtils.java @@ -19,6 +19,7 @@ import com.xabber.android.R; import com.xabber.android.data.Application; +import com.xabber.android.data.roster.RosterCacheManager; import java.text.DateFormat; import java.text.DateFormatSymbols; @@ -195,58 +196,61 @@ public static SimpleDateFormat getLogDateTimeFormat() { } public static String getLastActivityString(long lastActivityTime) { - if (lastActivityTime > 0) { - long timeAgo = System.currentTimeMillis()/1000 - lastActivityTime; - long time; - - if (timeAgo < 60) return Application.getInstance().getString(R.string.last_seen_now); - if (timeAgo < 3600) { - time = TimeUnit.SECONDS.toMinutes(timeAgo); - return Application.getInstance().getString(R.string.last_seen_minutes, String.valueOf(time)); + String result = RosterCacheManager.getInstance().getCachedLastActivityString(lastActivityTime); + + if (result == null || result.isEmpty()) { + result = ""; + if (lastActivityTime > 0) { + long timeAgo = System.currentTimeMillis()/1000 - lastActivityTime; + long time; + String sTime; + Date date = new Date(lastActivityTime * 1000); + Date today = new Date(); + Locale locale = Application.getInstance().getResources().getConfiguration().locale; + + if (timeAgo < 60) { + result = Application.getInstance().getString(R.string.last_seen_now); + + } else if (timeAgo < 3600) { + time = TimeUnit.SECONDS.toMinutes(timeAgo); + result = Application.getInstance().getString(R.string.last_seen_minutes, String.valueOf(time)); + + } else if (timeAgo < 7200) { + result = Application.getInstance().getString(R.string.last_seen_hours); + + } else if (isToday(date)) { + SimpleDateFormat pattern = new SimpleDateFormat("HH:mm", locale); + sTime = pattern.format(date); + result = Application.getInstance().getString(R.string.last_seen_today, sTime); + + } else if (isYesterday(date)) { + SimpleDateFormat pattern = new SimpleDateFormat("HH:mm", locale); + sTime = pattern.format(date); + result = Application.getInstance().getString(R.string.last_seen_yesterday, sTime); + + } else if (timeAgo < TimeUnit.DAYS.toSeconds(7)) { + SimpleDateFormat pattern = new SimpleDateFormat("HH:mm", locale); + sTime = pattern.format(date); + result = Application.getInstance().getString(R.string.last_seen_on_week, + getDayOfWeek(date, locale), sTime); + + } else if (date.getYear() == today.getYear()) { + SimpleDateFormat pattern = new SimpleDateFormat("d MMMM", locale); + sTime = pattern.format(date); + result = Application.getInstance().getString(R.string.last_seen_date, sTime); + + } else if (date.getYear() < today.getYear()) { + SimpleDateFormat pattern = new SimpleDateFormat("d MMMM yyyy", locale); + sTime = pattern.format(date); + result = Application.getInstance().getString(R.string.last_seen_date, sTime); + } + + if (!result.isEmpty()) + RosterCacheManager.getInstance().putLastActivityStringToCache(lastActivityTime, result); } - if (timeAgo < 7200) { - time = TimeUnit.SECONDS.toHours(timeAgo); - return Application.getInstance().getString(R.string.last_seen_hours); - } - - String sTime; - Date date = new Date(lastActivityTime * 1000); - Date today = new Date(); - Locale locale = Application.getInstance().getResources().getConfiguration().locale; - - if (isToday(date)) { - SimpleDateFormat pattern = new SimpleDateFormat("HH:mm", locale); - sTime = pattern.format(date); - return Application.getInstance().getString(R.string.last_seen_today, sTime); - } - - if (isYesterday(date)) { - SimpleDateFormat pattern = new SimpleDateFormat("HH:mm", locale); - sTime = pattern.format(date); - return Application.getInstance().getString(R.string.last_seen_yesterday, sTime); - } - - if (timeAgo < TimeUnit.DAYS.toSeconds(7)) { - SimpleDateFormat pattern = new SimpleDateFormat("HH:mm", locale); - sTime = pattern.format(date); - return Application.getInstance().getString(R.string.last_seen_on_week, - getDayOfWeek(date, locale), sTime); - } - - if (date.getYear() == today.getYear()) { - SimpleDateFormat pattern = new SimpleDateFormat("d MMMM", locale); - sTime = pattern.format(date); - return Application.getInstance().getString(R.string.last_seen_date, sTime); - } - - if (date.getYear() < today.getYear()) { - SimpleDateFormat pattern = new SimpleDateFormat("d MMMM yyyy", locale); - sTime = pattern.format(date); - return Application.getInstance().getString(R.string.last_seen_date, sTime); - } - return ""; } - else return ""; + + return result; } public static boolean isToday(Date date) { diff --git a/xabber/src/main/java/com/xabber/android/utils/Utils.java b/xabber/src/main/java/com/xabber/android/utils/Utils.java index 3e659e239d..fc647dc3c9 100644 --- a/xabber/src/main/java/com/xabber/android/utils/Utils.java +++ b/xabber/src/main/java/com/xabber/android/utils/Utils.java @@ -1,8 +1,14 @@ package com.xabber.android.utils; import android.content.Context; +import android.content.Intent; +import android.os.Build; import android.util.TypedValue; +import com.xabber.android.data.entity.AccountJid; +import com.xabber.android.data.push.SyncManager; +import com.xabber.android.service.XabberService; + import java.util.Calendar; import java.util.Date; @@ -22,4 +28,52 @@ public static boolean isSameDay(Long date1, Long date2) { cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR); } + public static void startXabberServiceCompat(Context context) { + startXabberServiceCompat(context, XabberService.createIntent(context)); + } + + public static void startXabberServiceCompatWithSyncMode(Context context, String pushNode) { + startXabberServiceCompat(context, + SyncManager.createXabberServiceIntentWithSyncMode(context, pushNode)); + } + + public static void startXabberServiceCompatWithSyncMode(Context context, AccountJid accountJid) { + startXabberServiceCompat(context, + SyncManager.createXabberServiceIntentWithSyncMode(context, accountJid)); + } + + private static void startXabberServiceCompat(Context context, Intent intent) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + context.startForegroundService(intent); + else context.startService(intent); + } + + public static String xmlEncode(String s) { + StringBuilder sb = new StringBuilder(); + char c; + for (int i = 0; i < s.length(); i++) { + c = s.charAt(i); + switch (c) { + case '<': + sb.append("<"); //$NON-NLS-1$ + break; + case '>': + sb.append(">"); //$NON-NLS-1$ + break; + case '&': + sb.append("&"); //$NON-NLS-1$ + break; + case '\'': + // In this implementation we use ' instead of ' because we encode XML, not HTML. + sb.append("'"); //$NON-NLS-1$ + break; + case '"': + sb.append("""); //$NON-NLS-1$ + break; + default: + sb.append(c); + } + } + return sb.toString(); + } } diff --git a/xabber/src/main/java/com/xabber/xmpp/sid/OriginIdElement.java b/xabber/src/main/java/com/xabber/xmpp/sid/OriginIdElement.java new file mode 100644 index 0000000000..6c02903a58 --- /dev/null +++ b/xabber/src/main/java/com/xabber/xmpp/sid/OriginIdElement.java @@ -0,0 +1,30 @@ +package com.xabber.xmpp.sid; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class OriginIdElement implements ExtensionElement { + + private String id; + + public OriginIdElement(String id) { + this.id = id; + } + + @Override + public String getNamespace() { + return UniqStanzaHelper.NAMESPACE; + } + + @Override + public String getElementName() { + return UniqStanzaHelper.ELEMENT_NAME_ORIGIN; + } + + @Override + public CharSequence toXML() { + return new XmlStringBuilder(OriginIdElement.this) + .attribute(UniqStanzaHelper.ATTRIBUTE_ID, id) + .closeEmptyElement(); + } +} \ No newline at end of file diff --git a/xabber/src/main/java/com/xabber/xmpp/sid/UniqStanzaHelper.java b/xabber/src/main/java/com/xabber/xmpp/sid/UniqStanzaHelper.java index a8de2864cc..cea488bc1e 100644 --- a/xabber/src/main/java/com/xabber/xmpp/sid/UniqStanzaHelper.java +++ b/xabber/src/main/java/com/xabber/xmpp/sid/UniqStanzaHelper.java @@ -9,9 +9,10 @@ public class UniqStanzaHelper { - private final static String ELEMENT_NAME = "stanza-id"; - private final static String NAMESPACE = "urn:xmpp:sid:0"; - private final static String ATTRIBUTE_ID = "id"; + final static String ELEMENT_NAME_ORIGIN = "origin-id"; + final static String ELEMENT_NAME = "stanza-id"; + final static String NAMESPACE = "urn:xmpp:sid:0"; + final static String ATTRIBUTE_ID = "id"; public static String getStanzaId(Message message) { StandardExtensionElement sidElement = message.getExtension(ELEMENT_NAME, NAMESPACE); @@ -19,4 +20,10 @@ public static String getStanzaId(Message message) { else return null; } + public static String getOriginId(Message message) { + StandardExtensionElement sidElement = message.getExtension(ELEMENT_NAME_ORIGIN, NAMESPACE); + if (sidElement != null) return sidElement.getAttributeValue(ATTRIBUTE_ID); + else return null; + } + } diff --git a/xabber/src/main/res/drawable/ic_google_plus.xml b/xabber/src/main/res/drawable/ic_google_plus.xml index 137d79d439..db35fc3ef8 100644 --- a/xabber/src/main/res/drawable/ic_google_plus.xml +++ b/xabber/src/main/res/drawable/ic_google_plus.xml @@ -1,22 +1,27 @@ - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/xabber/src/main/res/drawable/ic_google_plus_disabled.xml b/xabber/src/main/res/drawable/ic_google_plus_disabled.xml index cfed3eb9b4..bdf9656f5f 100644 --- a/xabber/src/main/res/drawable/ic_google_plus_disabled.xml +++ b/xabber/src/main/res/drawable/ic_google_plus_disabled.xml @@ -1,22 +1,23 @@ - - - - - - - \ No newline at end of file + + + + + + + diff --git a/xabber/src/main/res/layout-v22/view_bottom_navigation.xml b/xabber/src/main/res/layout-v22/view_bottom_navigation.xml index 88f1cdb747..363af5c35e 100644 --- a/xabber/src/main/res/layout-v22/view_bottom_navigation.xml +++ b/xabber/src/main/res/layout-v22/view_bottom_navigation.xml @@ -12,7 +12,7 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/xabber/src/main/res/layout/fragment_chat.xml b/xabber/src/main/res/layout/fragment_chat.xml index 4291ebf474..ecf2499dfd 100644 --- a/xabber/src/main/res/layout/fragment_chat.xml +++ b/xabber/src/main/res/layout/fragment_chat.xml @@ -21,15 +21,6 @@ android:id="@+id/root_view" android:elevation="24dp"> - - - - + android:gravity="center_vertical" + android:visibility="gone"> @@ -74,6 +75,16 @@ android:gravity="top" android:textColor="?android:attr/textColorSecondary" /> + - - diff --git a/xabber/src/main/res/layout/item_button_in_contact_list.xml b/xabber/src/main/res/layout/item_button_in_contact_list.xml index 8c464922ee..6470ac91b8 100644 --- a/xabber/src/main/res/layout/item_button_in_contact_list.xml +++ b/xabber/src/main/res/layout/item_button_in_contact_list.xml @@ -14,6 +14,4 @@ style="@style/Widget.AppCompat.Button.Borderless" /> - - \ No newline at end of file diff --git a/xabber/src/main/res/layout/item_chat_in_contact_list.xml b/xabber/src/main/res/layout/item_chat_in_contact_list.xml index c99bbb6a30..14aaf03d8b 100644 --- a/xabber/src/main/res/layout/item_chat_in_contact_list.xml +++ b/xabber/src/main/res/layout/item_chat_in_contact_list.xml @@ -160,6 +160,4 @@ - - \ No newline at end of file diff --git a/xabber/src/main/res/layout/item_chat_with_button.xml b/xabber/src/main/res/layout/item_chat_with_button.xml index 9ef7947f8b..5f48cb8ab0 100644 --- a/xabber/src/main/res/layout/item_chat_with_button.xml +++ b/xabber/src/main/res/layout/item_chat_with_button.xml @@ -187,6 +187,4 @@ - - \ No newline at end of file diff --git a/xabber/src/main/res/layout/item_contact_in_contact_list.xml b/xabber/src/main/res/layout/item_contact_in_contact_list.xml index f80e7beb38..d09c1b4f5b 100644 --- a/xabber/src/main/res/layout/item_contact_in_contact_list.xml +++ b/xabber/src/main/res/layout/item_contact_in_contact_list.xml @@ -72,7 +72,5 @@ android:text="1" /> - - \ No newline at end of file diff --git a/xabber/src/main/res/layout/item_ext_contact_in_contact_list.xml b/xabber/src/main/res/layout/item_ext_contact_in_contact_list.xml index 05a434e595..7b040116af 100644 --- a/xabber/src/main/res/layout/item_ext_contact_in_contact_list.xml +++ b/xabber/src/main/res/layout/item_ext_contact_in_contact_list.xml @@ -184,6 +184,4 @@ - - \ No newline at end of file diff --git a/xabber/src/main/res/layout/item_group_in_contact_list.xml b/xabber/src/main/res/layout/item_group_in_contact_list.xml index cdb76899b3..1e339dba7d 100644 --- a/xabber/src/main/res/layout/item_group_in_contact_list.xml +++ b/xabber/src/main/res/layout/item_group_in_contact_list.xml @@ -88,7 +88,5 @@ android:layout_marginLeft="2dp" /> - - \ No newline at end of file diff --git a/xabber/src/main/res/layout/item_message_crowdfunding.xml b/xabber/src/main/res/layout/item_message_crowdfunding.xml new file mode 100644 index 0000000000..dac350681e --- /dev/null +++ b/xabber/src/main/res/layout/item_message_crowdfunding.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/xabber/src/main/res/layout/item_message_incoming_crowdfunding.xml b/xabber/src/main/res/layout/item_message_incoming_crowdfunding.xml new file mode 100644 index 0000000000..da517c6180 --- /dev/null +++ b/xabber/src/main/res/layout/item_message_incoming_crowdfunding.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/xabber/src/main/res/layout/item_message_incoming_noflex_crowdfunding.xml b/xabber/src/main/res/layout/item_message_incoming_noflex_crowdfunding.xml new file mode 100644 index 0000000000..3a26b9c969 --- /dev/null +++ b/xabber/src/main/res/layout/item_message_incoming_noflex_crowdfunding.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/xabber/src/main/res/layout/item_message_noflex_crowdfunding.xml b/xabber/src/main/res/layout/item_message_noflex_crowdfunding.xml new file mode 100644 index 0000000000..6d769c4da5 --- /dev/null +++ b/xabber/src/main/res/layout/item_message_noflex_crowdfunding.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/xabber/src/main/res/layout/offline_shadow.xml b/xabber/src/main/res/layout/offline_shadow.xml deleted file mode 100644 index f2daacff7b..0000000000 --- a/xabber/src/main/res/layout/offline_shadow.xml +++ /dev/null @@ -1,7 +0,0 @@ - - \ No newline at end of file diff --git a/xabber/src/main/res/layout/view_bottom_navigation.xml b/xabber/src/main/res/layout/view_bottom_navigation.xml index c288b898ac..c377d45318 100644 --- a/xabber/src/main/res/layout/view_bottom_navigation.xml +++ b/xabber/src/main/res/layout/view_bottom_navigation.xml @@ -12,7 +12,7 @@ هل حقاً تريد حذف الحساب %s؟\n(لن يتم حذفه من الخادم، فقط من Xabber) أضف حساب - سجل حساب جديد + تسجيل حساب جديد حذف الحساب + إعدادات الحساب جارِ التسجيل - جارِ التخويل + جارِ الاتصال متصل جارِ الاتصال جارِ قطع الاتصال diff --git a/xabber/src/main/res/values-ar-rSA/chat_viewer.xml b/xabber/src/main/res/values-ar-rSA/chat_viewer.xml index 59dcedd44a..c17311622d 100644 --- a/xabber/src/main/res/values-ar-rSA/chat_viewer.xml +++ b/xabber/src/main/res/values-ar-rSA/chat_viewer.xml @@ -2,11 +2,11 @@ %1$s غير حالته: %2$s - %1$s مسح نص الحالة - %1$s انضم للاجتماع - %1$s تم طرده - أنت حالياً غير متصل. سيتم تسليم الرسائل التي ترسلها عندما تتصل مرة أخرى. - أرسل في %s + %1$s تم حذف الرسائل + %1$s انضم + %1$s تعرض للطرد + أنت غير متصل حاليًا. سيتم تسليم الرسائل التي ترسلها في المرة القادمة التي تتصل فيها. + ارسل في %s أكتب رسالتك هنا أرسل مسح سجل الرسائل @@ -16,9 +16,11 @@ طرف الاتصال غير متاح ملف غير موجود نسخ + إشارة اقتباس احذف من سجل الرسائل أعد محاولة الإرسال + عرض الرسالة حفظ السجل محلياً\nتخزين سجل الرسائل محلياً تم إرسال طلب انتباه تم طلب انتباهك @@ -49,13 +51,14 @@ %1$s يطلب عدم الإزعاج %1$s غير متاح %1$s متغيب لفترة طويلة - %1$s غير الموضوع إلى: %2$s - طرف الاتصال لا يدعم أو عطل خاصية طلب الانتباه - طلب انتباه - خيارات المحادثة - يقوم بالكتابة… - كتب نصاً… - كُتبت في %s + %1$s غير الموضوع الى: %2$s + جهة الاتصال لا تدعم أو تم تعطيل الاهتمام + تنبيه المكالمة + خيارات المحادثه + يكتب… + ادخل نصاً… + كتبت في %s + تاريخ التصدير تم تصدير سجل المحادثة لبطاقة الذاكرة SD %2$s إلى %1$s.html أرسل بعد التصدير @@ -69,10 +72,20 @@ تم طلب تشفير المحادثة باستخدام OTR لكن ليس لديك أداة تدعمها. استخدم Pidgin أو Gajim أو Adium لأنظمة الحاسوب، أو Xabber أو ChatSecure لأنظمة Android.\nتصفح http://otr.cypherpunks.ca/ لمزيد من المعلومات. ابدأ التشفير الرسالة المشفرة التي أرسلتها ليست مقروؤة لدى الطرف الآخر + لا يمكن فك تشفير الرسالة + رسالة معاد توجيهها %d تحقق OTR تحقق بالبصمة تحقق بالسؤال السري تحقق بكلمة سر + حدد الجهاز لتأسيس جلسة OTR: + جلسة OTR لم تبدأ. + جلسة OTR لم تبدأ. الاتصال غير متصل. + طلب التحقق من OTR + تحقق OTR في تقدم + افتح + اغلاق + رسالة مخفية استخدم الإعدادات العامة إظهار نص الرسالة إخفاء نص الرسالة @@ -81,4 +94,34 @@ فتح محادثة خاصة لا يوجد إذن لقراءة الملفات لا يوجد إذن لكتابة الملفات + لا إذن للكاميرا + وصف خاطئ: + ارسلت + تم التوصيل + وردت من التاريخ + خطأ + المرسلة من جهاز آخر + إرسال + لا يمكنك إرسال أكثر من 10 عناصر في وقت واحد + لا يوجد المزيد من الرسائل في التاريخ + وسيط + مشارك + زائر + الدردشات الأرشيف هو مبين + الدردشات الأرشيف مخفية + إشعار لهذه الدردشة + الانضمام إلى المؤتمر + اسحب لليمين لفتح الدردشات الحديثة + اسحب لليسار لفتح معلومات الاتصال + انسخ الرابط + تحميل + تم حفظ الصورة + تم نسخ الرابط إلى الحافظة + لا يمكن فتح الملف + جاري التحميل.. %s + رسائل غير مقروءة + مسؤول Xabber + تغذية الأخبار + دعم Xabber + جهة اتصال جديدة diff --git a/xabber/src/main/res/values-ar-rSA/contact_editor.xml b/xabber/src/main/res/values-ar-rSA/contact_editor.xml index 47e49edffa..e6c54a8054 100644 --- a/xabber/src/main/res/values-ar-rSA/contact_editor.xml +++ b/xabber/src/main/res/values-ar-rSA/contact_editor.xml @@ -5,7 +5,7 @@ اختر المجموعات المعرف (اختياري) مشاركة حالة الحساب %1$s مع طرف الاتصال؟ - معرف طرف الاتصال + اسم طرف الاتصال لم يتم العثور على طرف الاتصال حدد اسم المجموعة اسم المجموعة @@ -14,4 +14,11 @@ أضف مجموعة جديدة… تصريح إلغاء + جار معالجة الصورة... + خطأ أثناء قص الصورة + خطأ أثناء معالجة الصور + معلومات الخادم + قائمة قدرات الخادم + %1$s من %2$s يريد لبدء محادثة خاصة معك + قبول diff --git a/xabber/src/main/res/values-ar-rSA/contact_list.xml b/xabber/src/main/res/values-ar-rSA/contact_list.xml index 216961ba02..9407b40a8c 100644 --- a/xabber/src/main/res/values-ar-rSA/contact_list.xml +++ b/xabber/src/main/res/values-ar-rSA/contact_list.xml @@ -14,6 +14,7 @@ ليس لديك أي حسابات ليس لديك أطراف اتصال لا أحد متاح + لم يتم العثور على جهات اتصال لا حساب متصل متصل. جارِ تشغيل التطبيق… @@ -25,15 +26,27 @@ حظر جهة الاتصال هل حقاً تريد أن تحذف طرف الاتصال %1$s من حساب %2$s؟ رفض الاتصال + الارشيف + إلغاء الأرشفة + كتم إلى الأبد + كتم ل ١٥ دقيقة + كتم لساعه + كتم لساعتين + كتم ليوم واحد + كتم الصوت + الغاء كتم الصوت + عرض الارشيف خروج محادثات دائرة لا توجد مجموعات حذف مجموعة هل حقاً تريد حذف المجموعة %s؟ الأعضاء في تلك المجموعة سيظلون في قائمة الاتصال. أعد تسمية المجموعة + اعدادات الاشعارات حساب غير موجود لم يتم الاتصال طلب الاشتراك + إرسال جهة الاتصال خطأ في تدفق البيانات حفظ التغييرات…\nسيتم إغلاق التطبيق قريباً. اجتماعات @@ -54,4 +67,41 @@ تم إلغاء حظر جهات الاتصال بنجاح خطأ في إلغاء حظر جهات الاتصال هل حقاً تريد أن تحظر طرف الاتصال %1$s من حساب %2$s؟ + هل تريد حقًا إلغاء حظر جميع جهات الاتصال من الحساب %1$s؟ + إلغاء حظر الجميع + إلغاء حظر المحددين + اختار + سمة مظلمة جديدة متاحة الآن لـ Xabber. الانضمام إلى الجانب المظلم أو البقاء على الجانب ضوء؟ + مظلم + فاتح + عمل البطارية + يقوم جهازك ببعض عمليات تحسين البطارية الثقيلة على Xabber والتي قد تؤدي إلى تأخير الإخطارات أو حتى فقدان الرسائل. n/n/ سيُطلب منك الآن تعطيلها. + حذف الملفات القديمة + افتح + ارسل + مزامنة الإشارات المرجعية + حذف الكل + إزالة المحدد + هل تريد حقًا إلغاء حظر جميع جهات الاتصال من الحساب %1$s؟ + هل تريد حقا إزالة جميع الاجتماعات من الحساب؟ %1$s + لا يوجد محادثات في الارشيف + لايوجد دردشات غير مقروئه + تم أرشفة الدردشة + الحذف من الأرشيف + تراجع + المحادثات الأخيرة + دردشات غير مقروءة + دردشات المؤرشفة + كل المحادثات + لايوجد رسائل + اظهر كل الدردشات + جهات الاتصال + اسحب الدردشة إلى اليسار أو اليمين لأرشفتها + آخر ظهور منذ قليل + اخر ظهور منذ %1$s دقائق + اخر ظهور قبل ساعة + اخر ظهور %1$s + اخر ظهور ليلة امس %1$s + اخر ظهور %1$s في %2$s + اخر ظهور %1$s diff --git a/xabber/src/main/res/values-ar-rSA/contact_viewer.xml b/xabber/src/main/res/values-ar-rSA/contact_viewer.xml index 4ad03472f1..b5b4df9612 100644 --- a/xabber/src/main/res/values-ar-rSA/contact_viewer.xml +++ b/xabber/src/main/res/values-ar-rSA/contact_viewer.xml @@ -4,7 +4,7 @@ بيانات طرف الاتصال عميل هذا الجهاز - كنية + الاسم المستعار الاسم الكامل بادئة الاسم @@ -48,6 +48,20 @@ تعديل المعرِّف تعديل المجموعات حذف جهة اتصال + تعديل معلومات المستخدم تعديل تم حفظ معلومات الحساب بنجاح + لا يمكن حفظ معلومات مستخدم الحساب + حفظ... + اختيار من المعرض + اختيار صورة + ازالة الصوره + تغيير + لا يمكن الحصول على معلومات المؤتمر + الاسم + وصف + الموضوع + الركاب %d + الأجهزة المتصلة + البروفايل diff --git a/xabber/src/main/res/values-ar-rSA/notification_bar.xml b/xabber/src/main/res/values-ar-rSA/notification_bar.xml index 3c664b3b00..b5c9fdf441 100644 --- a/xabber/src/main/res/values-ar-rSA/notification_bar.xml +++ b/xabber/src/main/res/values-ar-rSA/notification_bar.xml @@ -1,7 +1,7 @@ - حساب + الحساب حسابات - @@ -20,6 +20,8 @@ - %1$d %2$s من %3$d %4$s %1$d %2$s من %3$s + %1$d جديد %2$s + %1$d جديد %2$s من %3$s %1$s: %2$s %1$d من %2$d %3$s متصل %1$d من %2$d %3$s متصلة @@ -39,4 +41,23 @@ مطلوب كلمة المرور انتبه طلب تصريح + محادثة خاصة المؤتمر + الردّ + غفوة + تاشير كمقروء + عرض الإشعارات المخصصة + قناة إعلام مخصصة للمحادثة: + قناة إعلام مخصصة للمجموعة: + قناة إعلام مخصصة للحساب: + قناة إعلام مخصصة للمحادثة: + مكالمات الاهتمام + إعلامات المكالمات الاهتمام + دردشة خاصة + إعلامات حول رسائل في الأحاديث الخاصة + مجموعة الدردشة + إعلامات حول الرسائل في دردشات المجموعة + استخدام الاتصال المستمر + الإخطار المستمرة لتأسيس اتصال لتلقي الرسائل + الأحداث + إخطارات للأحداث مثل: المحادثة المجموعة تدعو، طلبات الاشتراك، وطلبات OTR diff --git a/xabber/src/main/res/values-ar-rSA/preference_editor.xml b/xabber/src/main/res/values-ar-rSA/preference_editor.xml index 8f6b29458f..f22bd86064 100644 --- a/xabber/src/main/res/values-ar-rSA/preference_editor.xml +++ b/xabber/src/main/res/values-ar-rSA/preference_editor.xml @@ -1,25 +1,49 @@ + عمل البطارية + تحسين البطاريه معطل. لتمكينه انتقل إلى إعدادات الجهاز + تم تشغيل تحسينات البطارية مسح البيانات\nمسح كل البيانات المخزنة على الجهاز. هذا قد يحرر بعض المساحة. هل حقاً تريد حذف سجل المحادثات وكافة البيانات المحلية؟\nلن يتأثر الحساب ولا بيانات قائمة الاتصال. سيتم غلق التطبيق. أرسل بزر الإدخال\nيمكن إرسال الرسائل عند الضغط على مفتاح الإدخال تغييب ذاتي\nتعيين حالة الغياب ذاتياً عند قفل الشاشة + فرز جهات الاتصال أبجدي بالحالة جمِّع حسب الحساب\nجمِّع الأطراف في قائمة الاتصال حسب الحساب إظهار الصور الرمزية\nإظهار الصور الرمزية للأطراف في قائمة الاتصال + إظهار رسائل\nيظهر المستخدمين في قائمة جهات الاتصال أظهر المجموعات الخالية\nأظهر المجموعات التي ليس بها أطراف متاحة إظهار المجموعات\nإظهار المجموعات في قائمة الاتصال إظهار الأطراف الغير المتاحة سجل التصحيح\nكتابة الرسائل لسجل التصحيح (برجاء إعادة تشغيل البرنامج لتطبيق التغيير) كتابة ملف سجل التصحيح \nكتابة سجل التصحيح إلى ملف محلي (يمكنك مشاركته). ملفات السجل\nقائمة ملفات السجل المكتوبة. + تحميل كافة الرسائل من أرشيف\nوظيفة التصحيح. أيار/مايو ويعمل مع الأخطاء ملفات السجل إظهار أخطاء الاتصال\nإظهار اطارات منبثقة ﻷخطاء الاتصال + حصة تحطم وتشخيص البيانات\nمساعدة مطوري التطبيق تحسين شبير بالسماح بتبادل البيانات تحطم وتشخيص معهم (الرجاء إعادة تشغيل التطبيق لتطبيق التغييرات). + سيتم تطبيق التغييرات API شبير\nبعد إعادة التشغيل + إحضار علاوة تغذية الآن + بيانات الأعطال والتشخيص + Xabber يجمع تقارير الأخطاء المجهولة. يمكنك تعطيل فإنه في إعدادات التصحيح، ولكن سوف تعوق قدرتنا على توفير تجربة مراسلة موثوق بها للمستخدمين شبير. + إعدادات + إشارة الضوء + وميض LED على الإخطار + إشارة الضوء + وميض LED على الإشعار من المؤتمرات أيقونة شريط المهام\nاظهار مستمر للإخطار. هذا الخيار يمنع نظام Android من إزالة التطبيق من الذاكرة + الصوت + اختيار صوت التنبيهات + إهتزاز + تحديث إشعار التفضيلات + تفضيلات الإعلام مخصص تجاهل كافة الإعدادات الأخرى في الإشعار + يجب عليك أولاً حفظ عبارة + مظهر داكن فاتح + رموز المشاعر تعبيرات Android لا تعبيرات رسومية إعدادات الاتصال\nإعدادات الاتصال @@ -28,13 +52,25 @@ الإعدادات إشعارات\nضبط الإشعارات ضبط المظهر\nإعدادات المظهر + ضبط فلتر السبام + \nوجود وجود إعدادات + إعدادات خصوصية الخصوصية\n + بلا امان + قبول الرسائل من جهاة الاتصال فقط + قبول الرسائل من جهات الاتصال مع تحقق كابتشا + رسائل فقط من جهات الاتصال، بلا تحقق حول أظهر صورة الخلفية\nتعطيلها قد يحسن الأداء + حجم الخط كبير طبيعي صغير كبير جداً + إخفاء لوحة المفاتيح في الوضع العرضي + الصور الرمزية في الدردشة\nإظهار الصور الرمزية في كل رسالة داخل الدردشة + الصور الرمزية في الدردشة\nإظهار الصور الرمزية في كل رسالة داخل الدردشة + إظهار تغيير الحالات في الاجتماعات إرسال إخطار الكتابة\nإخطار الطرف الآخر أنك تكتب له رسالة تعديل الأولوية\nضبط الأولوية حسب الحالة المحددة. أولوية الحساب سيتم تجاوزها. @@ -46,14 +82,68 @@ أبقٍ اتصال WiFi نشطاً\nأبقٍ اتصال WiFi نشط في وضع السكون. سينقص عمر البطارية إعادة تعيين إعدادات الأطراف الغير متاحة\nمسح الإعدادات الفردية للمجموعات وأطراف الاتصال هل ترغب حقاً في مسح الإعدادات الفردية للمجموعات وأطراف الاتصال؟ + تنبيه على الرسالة الأولى + إعلام فقط في أول رسالة في المحادثة + معاينة الرسالة + إظهار نص رسالة من الأحاديث في منطقة الإعلام + معاينة الرسالة + إظهار نص رسالة من الأحاديث في منطقة الإعلام حظر بعض رسائل الحالة\nحظر رسائل \'الهويات في هذه الغرفة ليست مجهولة\' ‎%s‎ (لن تتلقَ رسائل من أي محادثة) حسابات XMPP\nإدارة الحسابات الأمان\nإعدادات الأمان تحقق من شهادة الخادم\nإخطار بمشاكل الشهادة على الاتصالات المشفرة وضع OTR + انتباه + الرد على طلبات الاهتمام الواردة + صوت الاشعار الاهتمام + إختر نغمة الرنين حمّل بطاقة vCard\nحمّل وحدّث البيانات الشخصية وصورة طرف الاتصال. عطّلها لتقليل استخدام بيانات الشبكة. تحميل صور\nتحميل الصور من رابط الملف تلقائياً. تعطيل للحد من استخدام بيانات الهاتف. وضع النسخة الكربونية\nقد تكون غير مستقرة!سوف تشارك المحادثات لنفس الحساب على هذا العميل. عطّلها لتقليل استخدام بيانات الشبكة. + Resolver + MiniDNSResolver (تجريبية) + عبارات مفتاحية + عبارات مفتاحية + إنشاء إعلامات مخصصة للرسائل المستلمة التي تحتوي على عبارات محددة + يجب اختيار تصفية واحدة على الأقل إعدادات + واجهة المستخدم + إعدادات + حول + المظهر العام + السلوك + قائمة جهات الاتصال + الجهات الغير متصلة + إعادة تعيين نغمة الاشعارات + إعدادات الاتصال + إعدادات متقدمة + التوافر: + إشعارات الرسائل + إشعارات المجموعات + الاشعارات داخل التطبيق + عبارات مفتاحية + إشعار مخصص + تنبية + تنبيه عن رسائل جديدة في الأحاديث + تنبية + تنبيه عن رسائل جديدة في الأحاديث + الافتراضي + اهتزاز قصيرة + الإهتزاز طويل + فقط في الوضع الصامت + أصوات في التطبيق + قراءة الأصوات في الرسالة في آخر محادثة + يهتز في التطبيق + قراءة الأصوات في الرسالة في آخر محادثة + معاينة داخل التطبيق + قراءة الأصوات في الرسالة في آخر محادثة + أصوات في المحادثه + قراءة الأصوات في الرسالة في آخر محادثة + إعادة تعيين + أن تعيين كافة الإخطار الإعدادات إلى الاعدادات القديمه + اعادة تعيين إعدادات الإشعارات + تم إعادة تعيين الإعدادات + إزالة كافة التخصيصات؟ + أن إزالة كافة إعدادات إعلام مخصصة (للاتصالات، قائمة بأسماء المجموعات والحسابات) diff --git a/xabber/src/main/res/values-ar-rSA/words.xml b/xabber/src/main/res/values-ar-rSA/words.xml index a8731cd86a..75748dd7b3 100644 --- a/xabber/src/main/res/values-ar-rSA/words.xml +++ b/xabber/src/main/res/values-ar-rSA/words.xml @@ -1,7 +1,7 @@ - بعد 10 دقائف + بعد 10 دقائق بعد 15 دقيقة بعد ساعة بعد دقيقة @@ -15,4 +15,17 @@ مطلوب آلياً حفظ + إلغاء + إزالة + تخطي + تخطي على أي حال + تحذير! + إعدادات + حسناً + تسجيل الدخول + مشاركة + الإرسال إلى + احصل عليه + بحث + مجاني diff --git a/xabber/src/main/res/values-ca-rES/chat_viewer.xml b/xabber/src/main/res/values-ca-rES/chat_viewer.xml index fc58859b54..9951c7bccc 100644 --- a/xabber/src/main/res/values-ca-rES/chat_viewer.xml +++ b/xabber/src/main/res/values-ca-rES/chat_viewer.xml @@ -108,7 +108,6 @@ Arxivar xats mostrats Arxivar xats ocults Notificació per aquest xat - El servidor que està utilitzant no dóna suport transferència d\'arxiu. Pot provar el servidor xabber.org. Uneix-te a la conferència Llisqueu a la dreta per obrir els xats recents Llisqueu a l\'esquerra per obrir la informació de contacte diff --git a/xabber/src/main/res/values-cs-rCZ/chat_viewer.xml b/xabber/src/main/res/values-cs-rCZ/chat_viewer.xml index 36a0ac03d3..36d5aaaacc 100644 --- a/xabber/src/main/res/values-cs-rCZ/chat_viewer.xml +++ b/xabber/src/main/res/values-cs-rCZ/chat_viewer.xml @@ -108,7 +108,6 @@ Archív chatů zobrazen Arcvhív chatů skryt Oznámení pro tento chat - Server, který používáte nepodporuje přenos souborů. Můžete vyzkoušet na serveru xabber.org. Připojit se do konference Potáhnutím doprava otevřete současné chaty Potáhnutím doleva otevřete informace o kontaktu diff --git a/xabber/src/main/res/values-de-rDE/chat_viewer.xml b/xabber/src/main/res/values-de-rDE/chat_viewer.xml index 6660684084..c401e9f6a9 100644 --- a/xabber/src/main/res/values-de-rDE/chat_viewer.xml +++ b/xabber/src/main/res/values-de-rDE/chat_viewer.xml @@ -108,7 +108,6 @@ Angezeigte Chats archivieren Verborgene Chats archivieren Benachrichtigung für diesen Chat - Der Server, den Sie verwenden unterstützt keine Dateiübertragung. Sie können es mit dem Server xabber.org versuchen. Konferenz beitreten Wischen Sie nach rechts um den letzten Chat zu öffnen Wischen Sie nach links, um Kontaktinformationen zu öffnen diff --git a/xabber/src/main/res/values-el-rGR/chat_viewer.xml b/xabber/src/main/res/values-el-rGR/chat_viewer.xml index bd084aac91..3cf551ff5b 100644 --- a/xabber/src/main/res/values-el-rGR/chat_viewer.xml +++ b/xabber/src/main/res/values-el-rGR/chat_viewer.xml @@ -108,7 +108,6 @@ Αρχειοθέτηση εμφανιζόμενων συνομιλιών Αρχειοθέτηση κρυμμένων συνομιλιών Ειδοποίηση για αυτήν τη συνομιλία - Ο διακομιστής που χρησιμοποιείτε δεν υποστηρίζει μεταφορά αρχείων. Μπορείτε να δοκιμάσετε το διακομιστή xabber.org. Συμμετοχή στη συνεδρία Σαρώστε προς τα δεξιά για να ανοίξετε τις πρόσφατες συνομιλίες Σαρώστε προς τα αριστερά για να ανοίξετε στοιχεία επικοινωνίας diff --git a/xabber/src/main/res/values-es-rES/chat_viewer.xml b/xabber/src/main/res/values-es-rES/chat_viewer.xml index acc244700a..434aa66f30 100644 --- a/xabber/src/main/res/values-es-rES/chat_viewer.xml +++ b/xabber/src/main/res/values-es-rES/chat_viewer.xml @@ -108,7 +108,6 @@ Muestra archivo de chats Archivo de chats oculto Notificación para este chat - El servidor que está utilizando no soporta la transferencia de archivos. Puede probar el servidor xabber.org. Unirse a la sala de charla Deslice a la derecha para abrir las charlas recientes Deslice a la izquierda para abrir la información de contacto diff --git a/xabber/src/main/res/values-fi-rFI/chat_viewer.xml b/xabber/src/main/res/values-fi-rFI/chat_viewer.xml index 921db6d01d..979f7b0ad9 100644 --- a/xabber/src/main/res/values-fi-rFI/chat_viewer.xml +++ b/xabber/src/main/res/values-fi-rFI/chat_viewer.xml @@ -106,7 +106,6 @@ Keskustelun arkisto näytetty Keskustelu arkisto piilotettu Ilmoitus tälle keskustelulle - Palvelin jota käytät ei tue tiedostonsiirtoa. Voit kokeilla xabber.org palvelinta. Liity konferenssiin Pyyhkäise oikealle avataksesi viimeaikaiset keskustelut Pyyhkäise vasemmalle avataksesi yhteystiedon tiedot diff --git a/xabber/src/main/res/values-fil-rPH/chat_viewer.xml b/xabber/src/main/res/values-fil-rPH/chat_viewer.xml index d719368cfa..e849dd7d16 100644 --- a/xabber/src/main/res/values-fil-rPH/chat_viewer.xml +++ b/xabber/src/main/res/values-fil-rPH/chat_viewer.xml @@ -106,7 +106,6 @@ I-archive ang mga chats na pinapakita Nakatago ang mga chat sa archive Abiso para sa chat na ito - Ang server na iyong ginagamit ay hindi sumusuporta sa paglilipat ng file. Maaari mong subukan ang xabber.org server. Sumali sa kumperensya Mag-swipe pakanan upang buksan ang mga kamakailang mga chat Mag-swipe pakaliwa upang buksan ang impormasyon ng kontak diff --git a/xabber/src/main/res/values-in-rID/chat_viewer.xml b/xabber/src/main/res/values-in-rID/chat_viewer.xml index a7fd4bc8c4..f3962a0050 100644 --- a/xabber/src/main/res/values-in-rID/chat_viewer.xml +++ b/xabber/src/main/res/values-in-rID/chat_viewer.xml @@ -108,7 +108,6 @@ Arsipkan obrolan tertampil Arsipkan obrolan tersembunyi Notifikasi obrolan ini - Server yang Anda gunakan tidak mendukung pengiriman berkas. Silakan coba server xabber.org. Gabung konferensi Geser ke kanan untuk membuka obrolan terbaru Geser ke kiri untuk membuka informasi kontak diff --git a/xabber/src/main/res/values-it-rIT/chat_viewer.xml b/xabber/src/main/res/values-it-rIT/chat_viewer.xml index 7159f69960..c55770bf3e 100644 --- a/xabber/src/main/res/values-it-rIT/chat_viewer.xml +++ b/xabber/src/main/res/values-it-rIT/chat_viewer.xml @@ -108,7 +108,6 @@ Chat archiviate mostrate Chat archiviate nascoste Notifica per questa chat - Il server che stai usando non supporta il trasferimento di file. Puoi provare il server xabber.org . Entra nella conferenza Scorri verso destra per aprire le chat recenti Scorri verso sinistra per aprire le informazioni di contatto diff --git a/xabber/src/main/res/values-iw-rIL/chat_viewer.xml b/xabber/src/main/res/values-iw-rIL/chat_viewer.xml index 3c04cce787..ccb7fda75e 100644 --- a/xabber/src/main/res/values-iw-rIL/chat_viewer.xml +++ b/xabber/src/main/res/values-iw-rIL/chat_viewer.xml @@ -105,7 +105,6 @@ התכתבויות מהארכיון מוצגות התכתבויות מהארכיון מוסתרות התראה להתכתבות זו - השרת בו נעשה שימוש כרגע אינו תומך בהעברת קבצים. ניתן לנסות את השרת xabber.org. הצטרפות לוועידה העתקת קישור הורדה diff --git a/xabber/src/main/res/values-ja-rJP/chat_viewer.xml b/xabber/src/main/res/values-ja-rJP/chat_viewer.xml index 6f5b9daff7..07e4feba4b 100644 --- a/xabber/src/main/res/values-ja-rJP/chat_viewer.xml +++ b/xabber/src/main/res/values-ja-rJP/chat_viewer.xml @@ -106,7 +106,6 @@ アーカイブ チャットを表示しました アーカイブ チャットを非表示にしました このチャットの通知 - 使用しているサーバーはファイル転送をサポートしていません。Xabber.org サーバーを試してください。 会議に参加 右にスワイプすると、最近のチャットを開きます 左にスワイプすると、連絡先情報を開きます diff --git a/xabber/src/main/res/values-ms-rMY/chat_viewer.xml b/xabber/src/main/res/values-ms-rMY/chat_viewer.xml index bad59c40bd..cb51aeed86 100644 --- a/xabber/src/main/res/values-ms-rMY/chat_viewer.xml +++ b/xabber/src/main/res/values-ms-rMY/chat_viewer.xml @@ -106,7 +106,6 @@ Arkib chat ditunjukkan Arkib chat sembunyi Notifikasi untuk chat ini - Pelayan yang anda gunakan tiada sokongan untuk pemindahan fail. Anda boleh cuba di xabber.org server. Sertai persidangan Leret kanan untuk buka chat sebelumnya Leret kiri untuk buka info kenalan diff --git a/xabber/src/main/res/values-nb-rNO/chat_viewer.xml b/xabber/src/main/res/values-nb-rNO/chat_viewer.xml index e47dac3294..7f028c5ec3 100644 --- a/xabber/src/main/res/values-nb-rNO/chat_viewer.xml +++ b/xabber/src/main/res/values-nb-rNO/chat_viewer.xml @@ -106,7 +106,6 @@ Arkiver viste sludringer Arkiver skjulte sludringer Merknad for denne sludringen - Tjeneren du bruker støtter ikke filoverføringer. Du kan prøve xabber.org-tjeneren. Ta del i konferanse Dra til høyre for å åpne nylige sludringer Dra til venstre for å åpne kontaktinfo diff --git a/xabber/src/main/res/values-pl-rPL/chat_viewer.xml b/xabber/src/main/res/values-pl-rPL/chat_viewer.xml index efe49cb0b6..2832ad0595 100644 --- a/xabber/src/main/res/values-pl-rPL/chat_viewer.xml +++ b/xabber/src/main/res/values-pl-rPL/chat_viewer.xml @@ -108,7 +108,6 @@ Archiwum chatów widoczne Archiwum czatów ukryte Powiadomienie dla tego czatu - Server, którego używasz nie obsługuje transferu plików. Możesz spróbować na serwerze xabber.org. Dołącz do konferencji Przesuń palcem w prawo, aby otworzyć ostatnie rozmowy Przesuń w lewo, aby otworzyć informacje kontaktowe diff --git a/xabber/src/main/res/values-pt-rBR/chat_viewer.xml b/xabber/src/main/res/values-pt-rBR/chat_viewer.xml index 6bd9aad03d..6e328f018b 100644 --- a/xabber/src/main/res/values-pt-rBR/chat_viewer.xml +++ b/xabber/src/main/res/values-pt-rBR/chat_viewer.xml @@ -108,7 +108,6 @@ Mostrar arquivos do bate-papo Arquivos de bate-papo ocultos Notificação para este bate-papo - O servidor que você está usando não suporta a transferência de arquivos. Você pode tentar o servidor xabber.org. Junte-se a conferência Deslize para a direita para abrir as conversas recentes Deslize para a esquerda para abrir as informações de contato diff --git a/xabber/src/main/res/values-ru-rRU/account_editor.xml b/xabber/src/main/res/values-ru-rRU/account_editor.xml index 36662e5968..678dc9c6ec 100644 --- a/xabber/src/main/res/values-ru-rRU/account_editor.xml +++ b/xabber/src/main/res/values-ru-rRU/account_editor.xml @@ -42,7 +42,7 @@ Требовать TLS шифрование при подключении к серверу Включить протокол шифрования TLS Подключаться через анонимную сеть TOR (необходим Orbot proxy) - Неправильное имя пользователя. Просмотрите подсказку внизу экрана + Неправильное имя пользователя Для использования TOR необходимо установить приложение Orbot и активировать в нём передачу данных. Вы хотите установить его из Google Play? Установить Orbot? Установить Orbot diff --git a/xabber/src/main/res/values-ru-rRU/chat_viewer.xml b/xabber/src/main/res/values-ru-rRU/chat_viewer.xml index f713ec8246..2b526de02b 100644 --- a/xabber/src/main/res/values-ru-rRU/chat_viewer.xml +++ b/xabber/src/main/res/values-ru-rRU/chat_viewer.xml @@ -110,7 +110,7 @@ Архивные диалоги отображены Архивные диалоги скрыты Уведомления для этого чата - Используемый сервер не поддерживает передачу данных. Попробуйте использовать сервер xabber.org + Похоже, сервер %1$s, не поддерживает передачу данных. Возможно, Xabber еще не получил информацию о сервере, или произошел какой-то сбой.\n\nОбновите информацию о сервере в разделе Настройки учетной записи → Информация о сервере. Если это не поможет, подумайте об использовании сервера, который наверняка поддерживает передачу файлов. Одним из таких серверов является xabber.org Присоединиться Смахните вправо чтобы открыть список недавних чатов Смахните влево чтобы увидеть данные собеседника diff --git a/xabber/src/main/res/values-ru-rRU/notification_bar.xml b/xabber/src/main/res/values-ru-rRU/notification_bar.xml index e78bfa9b4b..05256fc23f 100644 --- a/xabber/src/main/res/values-ru-rRU/notification_bar.xml +++ b/xabber/src/main/res/values-ru-rRU/notification_bar.xml @@ -36,6 +36,7 @@ %1$d из %2$d %3$s ожидают переподключения %1$d %2$s не в сети + Выполняется синхронизация Вы хотите подключиться к конференции? Нажмите, чтобы прервать. Требуется ввести пароль diff --git a/xabber/src/main/res/values-ru-rRU/preference_editor.xml b/xabber/src/main/res/values-ru-rRU/preference_editor.xml index e69a605d81..d64d9d63e5 100644 --- a/xabber/src/main/res/values-ru-rRU/preference_editor.xml +++ b/xabber/src/main/res/values-ru-rRU/preference_editor.xml @@ -100,6 +100,7 @@ Загружать vCard\nЗагружать и обновлять персональную информацию и аватары контактов. Отключение поможет сэкономить трафик. Загружать изображения\nЗагружать изображения по ссылкам автоматически. Отключите для экономии трафика. Сообщения carbons\nПринимать сообщения других сессий Вашей учетной записи. Отключение поможет сэкономить трафик. + Сжатие изображений\nСжимать изображения при загрузке на сервер. Включение поможет сэкономить трафик. Некоторые серверы могут иметь ограничение размера загружаемого файла. Включите, чтобы обойти ограничение. DNSJavaResolver MiniDNSResolver (экспериментальный) Ключевые фразы @@ -123,6 +124,7 @@ Уведомления в приложении Ключевые фразы Специальные уведомления + Другое Оповещение Уведомлять о новых сообщениях в чатах Оповещение @@ -138,7 +140,7 @@ Предпросмотр в приложении Предпросмотр при сообщениях в других чатах Звук в текущем чате - Воспроизводить уведомление при сообщениях в текущем чате + Воспроизводить уведомление при получении и отправке сообщений в текущем чате Сброс Все настройки уведомлений будут установлены в значения по умолчанию Сбросить настройки уведомлений? diff --git a/xabber/src/main/res/values-sk-rSK/chat_viewer.xml b/xabber/src/main/res/values-sk-rSK/chat_viewer.xml index c0e241e1e2..db2b1667f7 100644 --- a/xabber/src/main/res/values-sk-rSK/chat_viewer.xml +++ b/xabber/src/main/res/values-sk-rSK/chat_viewer.xml @@ -106,7 +106,6 @@ Archívy chatov zobrazené Archívy chatov skryté Oznámenie pre tento chat - Server ktorý používate nepodporuje prenos súborov. Môžete skúsiť xabber.org server. Pridať sa do konferencie Potiahnutím doprava otvorte súčasné chaty Potiahnutím doľava otvorte informácie o kontakte diff --git a/xabber/src/main/res/values-sl-rSI/chat_viewer.xml b/xabber/src/main/res/values-sl-rSI/chat_viewer.xml index fc50ae482c..a8dc8ba556 100644 --- a/xabber/src/main/res/values-sl-rSI/chat_viewer.xml +++ b/xabber/src/main/res/values-sl-rSI/chat_viewer.xml @@ -108,7 +108,6 @@ Prikazani arhivirani klepeti Arhiviraj skrite pogovore Obvestila za ta pogovor - Server ki ga uporabljaš ne podpira prenosa podatkov. Lahko poizkusiš xabber.org server. Pridruži se konferenci Podrsaj desno da odprete prejšnje pogovore Podrsaj levo da odprete informacije stika diff --git a/xabber/src/main/res/values-sv-rSE/chat_viewer.xml b/xabber/src/main/res/values-sv-rSE/chat_viewer.xml index 0c8727b295..5ba8ce5f40 100644 --- a/xabber/src/main/res/values-sv-rSE/chat_viewer.xml +++ b/xabber/src/main/res/values-sv-rSE/chat_viewer.xml @@ -108,7 +108,6 @@ Arkiverade chattar visas Arkiverade chattar är dolda Den här chattens aviseringar - Servern som du använder har inte stöd för filöverföring. Du kan prova servern xabber.org. Anslut till konferens Dra åt höger för att visa senaste chattar Dra åt vänster för att visa kontaktinformation diff --git a/xabber/src/main/res/values-tr-rTR/chat_viewer.xml b/xabber/src/main/res/values-tr-rTR/chat_viewer.xml index c853c9ac0b..a7cdffb7f6 100644 --- a/xabber/src/main/res/values-tr-rTR/chat_viewer.xml +++ b/xabber/src/main/res/values-tr-rTR/chat_viewer.xml @@ -106,7 +106,6 @@ Gösterilen sohbetleri arşivle Gizlenen sohbetleri arşivle Bu sohbet için bildirimler - Kullandığınız sunucu dosya aktarımını desteklemiyor. Bunun için xabber.org sunucusunu deneyebilirsiniz. Konferansa katıl Son sohbetleri açmak için sağa kaydırın Kişi bilgisini görüntülemek için sola kaydırın diff --git a/xabber/src/main/res/values-uk-rUA/chat_viewer.xml b/xabber/src/main/res/values-uk-rUA/chat_viewer.xml index 01bf518359..2be9337d43 100644 --- a/xabber/src/main/res/values-uk-rUA/chat_viewer.xml +++ b/xabber/src/main/res/values-uk-rUA/chat_viewer.xml @@ -108,7 +108,6 @@ Архівні діалоги відображені Архівні діалоги приховано Сповіщення для цього чату - Цей сервер не підтримує передачу даних. Спробуйте сервер xabber.org. Долучитися Проведіть праворуч щоб відкрити крайні чати Проведіть ліворуч щоб відкрити контактну інформацію diff --git a/xabber/src/main/res/values-zh-rCN/chat_viewer.xml b/xabber/src/main/res/values-zh-rCN/chat_viewer.xml index 81de7711fa..fec332eaab 100644 --- a/xabber/src/main/res/values-zh-rCN/chat_viewer.xml +++ b/xabber/src/main/res/values-zh-rCN/chat_viewer.xml @@ -108,7 +108,6 @@ 显示的存档聊天记录 隐藏的存档聊天记录 此聊天的通知 - 您正在使用的服务器不支持文件传输。您可以尝试 xabber.org 服务器。 加入群聊 向右滑动打开最近的聊天记录 向左滑动以打开联系人信息 diff --git a/xabber/src/main/res/values/about_viewer.xml b/xabber/src/main/res/values/about_viewer.xml index 931f8d0ed9..f2f30a4a15 100644 --- a/xabber/src/main/res/values/about_viewer.xml +++ b/xabber/src/main/res/values/about_viewer.xml @@ -3,7 +3,7 @@ open source XMPP client About - Xabber is an open-source XMPP messenger for Android platform. It is built around open standards, interoperability, design and user experience. Xabber supports many popular XMPP extension protocols, Off-The-Record Chat Encryption and is available in multiple languages. + Xabber is an open source XMPP messenger for Android, iOS and Web platforms. It is build around open standards, interoperability, design and great user experience. Versions of Xabber for every platform are built to provide a continuous chat experience between them. XMPP protocol Extensible Messaging and Presence Protocol (XMPP) is a communications protocol for message-oriented middleware based on XML (Extensible Markup Language). It enables the near-real-time exchange of structured yet extensible data between any two or more network entities. The protocol was originally named Jabber, and was developed by the Jabber open-source community in 1999 for near real-time instant messaging (IM),presence information, and contact list maintenance. @@ -13,7 +13,7 @@ XMPP is highly extensible, via extensions known as XEPs (XMPP Extension Protocol). Xabber supports a number of popular XEPs that are essential to providing great chat experience for our users. Developers - Xabber was originally developed by Redsolution, Inc. — an international software and services company based in Russia and United States. Xabber was licensed under GNU/GPL v.3 license early in 2013. Since then, a number of individuals joined Xabber as developers, testers and translators.\n\nOur goal is to create a stable, reliable and user friendly ecosystem for instant messaging that does not rely on proprietary services. We welcome anyone who believes in open standards and free information interchange to take part in moving Xabber forward.\n\nFollow us on Twitter and Github. + Xabber for Android was originally developed by Redsolution — an international software and services company currently based in Estonia. Since then, a number of individuals joined Xabber as developers, testers and translators.\n\nOur goal is to create a stable, reliable, interoperable and user friendly ecosystem for instant messaging that does not rely on proprietary services and data silos. We welcome anyone who believes in open standards and free information interchange to take part in moving Xabber forward.\n\nFollow us on Twitter and Github. http://www.redsolution.com/ http://www.xabber.com diff --git a/xabber/src/main/res/values/account_editor.xml b/xabber/src/main/res/values/account_editor.xml index e032942acf..77c96289d7 100644 --- a/xabber/src/main/res/values/account_editor.xml +++ b/xabber/src/main/res/values/account_editor.xml @@ -41,7 +41,7 @@ Require TLS encryption when connect to server Enable TLS cryptographic protocol Use TOR anonymity network (requires Orbot proxy) - Incorrect user name. Check help text below for details. + Incorrect user name In order to process using TOR you must have Orbot installed and activated to proxy traffic through it. Would you like to install it from Google Play? Install Orbot? Install Orbot @@ -104,5 +104,8 @@ Synchronization Synchronization with Xabber Account + Push notifications + Enable push notifications + Server does not support bookmarks diff --git a/xabber/src/main/res/values/account_list.xml b/xabber/src/main/res/values/account_list.xml index 5f1bb990f2..5a8e2c5698 100644 --- a/xabber/src/main/res/values/account_list.xml +++ b/xabber/src/main/res/values/account_list.xml @@ -12,4 +12,8 @@ Disconnecting Offline Waiting to reconnect + + Push-notifications disabled + Push-notifications not supported + Push-notifications enabled diff --git a/xabber/src/main/res/values/chat_viewer.xml b/xabber/src/main/res/values/chat_viewer.xml index dc6a529d46..a33394dbe0 100644 --- a/xabber/src/main/res/values/chat_viewer.xml +++ b/xabber/src/main/res/values/chat_viewer.xml @@ -116,7 +116,7 @@ Archive chats hidden Notification for this chat - The server you are using does not support file transfer. You can try the xabber.org server. + %1$s server does not seem to support file transfer. This may happen because Xabber didn\'t yet receive server capabilities, or because of some glitch.\n\nRefresh server info in Account settings → Server info. If that does not help, consider using a server that definitely does support file transfer. One such server is xabber.org Join conference Swipe right to open recent chats diff --git a/xabber/src/main/res/values/ids.xml b/xabber/src/main/res/values/ids.xml new file mode 100644 index 0000000000..30020bb66b --- /dev/null +++ b/xabber/src/main/res/values/ids.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/xabber/src/main/res/values/notification_bar.xml b/xabber/src/main/res/values/notification_bar.xml index 5e33f5b0ed..1f4629cd06 100644 --- a/xabber/src/main/res/values/notification_bar.xml +++ b/xabber/src/main/res/values/notification_bar.xml @@ -35,6 +35,7 @@ - %1$d %2$s offline + Synchronization in progress Do you want to enter conference? Click here to abort it. Password required diff --git a/xabber/src/main/res/values/preference_editor.xml b/xabber/src/main/res/values/preference_editor.xml index 61c4304065..41a70aa840 100644 --- a/xabber/src/main/res/values/preference_editor.xml +++ b/xabber/src/main/res/values/preference_editor.xml @@ -22,11 +22,13 @@ Debug log\nWrite messages to debug log (please restart application to apply changes) Write file log\nWrite debug log to local file (you can share it). Log files\nList of written log files. + Push-notification log Download all messages from archive\nDebug function. May works with errors Log files Show connection errors\nDisplay connection exceptions pop-ups Share crash and diagnostics data\nHelp app developers improve Xabber by allowing to share crash and diagnostics data with them (please restart application to apply changes). Use development Xabber API\nChanges will be applied after restart + Sync bookmarks on start Fetch crowdfunding feed now Crash and diagnostics data @@ -115,6 +117,7 @@ Load vCard\nLoad and update contact\'s personal info and avatar. Disable to reduce traffic usage. Load images\nLoad images from file URL automatically. Disable to reduce traffic usage. Carbon-copied mode\nWill share sessions for the same account on this client. Disable to reduce traffic usage. + Compress images\nCompress images on upload to reduce traffic usage. Some servers may have upload size restrictions, use this option to avoid it. DNSJavaResolver MiniDNSResolver (experimental) @@ -142,6 +145,7 @@ In-app notification Key phrases Custom notifications + Other Alert Alert about new messages in chats @@ -160,7 +164,7 @@ In-app preview Show preview on message in another chat In-chat sounds - Play sound on message in current chat + Play sound on receiving and sending message in current chat Reset All notification settings would be set to default values diff --git a/xabber/src/main/res/values/preferences.xml b/xabber/src/main/res/values/preferences.xml index 40d9cba7f2..e73dc077e8 100644 --- a/xabber/src/main/res/values/preferences.xml +++ b/xabber/src/main/res/values/preferences.xml @@ -34,6 +34,7 @@ crowdfunding_leader_last_load_timestamp_key first_app_run_timestamp_key crowdfunding_last_position_key + enabled_push_nodes last_sync_date_key @string/last_sync_date_never @@ -352,6 +353,9 @@ connection_use_carbons_new true + connection_compress_image_on_upload + true + connection_dns_resolver_type connection_dns_resolver_type_dns_java_resolver @@ -375,6 +379,7 @@ false debug_log_activity + push_log_activity_key debug_crash_reports true @@ -383,7 +388,9 @@ debug_download_all_messages_key debug_develop_xabber_api_key false + true debug_fetch_crowdfunding_feed_key + debug_sync_bookmarks_on_start_key @string/about_viewer diff --git a/xabber/src/main/res/xml/preference_connection.xml b/xabber/src/main/res/xml/preference_connection.xml index 033ab221db..0530d4d233 100644 --- a/xabber/src/main/res/xml/preference_connection.xml +++ b/xabber/src/main/res/xml/preference_connection.xml @@ -4,12 +4,6 @@ - - - + - - - - - - - + \ No newline at end of file diff --git a/xabber/src/main/res/xml/preference_notifications.xml b/xabber/src/main/res/xml/preference_notifications.xml index 272154309f..9693e0e51c 100644 --- a/xabber/src/main/res/xml/preference_notifications.xml +++ b/xabber/src/main/res/xml/preference_notifications.xml @@ -130,6 +130,17 @@ android:targetClass="com.xabber.android.ui.preferences.PhraseList"/> + + + + + diff --git a/xabber/src/open/res/values/social_keys.xml b/xabber/src/open/res/values/social_keys.xml index e42f1271e6..e107dbdad2 100644 --- a/xabber/src/open/res/values/social_keys.xml +++ b/xabber/src/open/res/values/social_keys.xml @@ -15,4 +15,5 @@ temp temp + temp \ No newline at end of file diff --git a/xabber/src/test/java/com/xabber/android/data/extension/references/RefFileTest.java b/xabber/src/test/java/com/xabber/android/data/extension/references/RefFileTest.java new file mode 100644 index 0000000000..d34d696e9a --- /dev/null +++ b/xabber/src/test/java/com/xabber/android/data/extension/references/RefFileTest.java @@ -0,0 +1,30 @@ +package com.xabber.android.data.extension.references; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class RefFileTest { + String xml; + RefFile.Builder builder; + + @Before + public void setUp() throws Exception { + builder = RefFile.newBuilder(); + builder.setMediaType("image/jpeg"); + builder.setName("summit.jpg"); + builder.setHeight(150); + builder.setWidth(160); + builder.setSize(3032449); + builder.setDesc("Photo from the summit."); + + xml = "image/jpegsummit.jpg150160" + + "3032449Photo from the summit."; + } + + @Test + public void toXML() { + assertEquals(xml, builder.build().toXML().toString()); + } +} \ No newline at end of file diff --git a/xabber/src/test/java/com/xabber/android/data/extension/references/RefMediaTest.java b/xabber/src/test/java/com/xabber/android/data/extension/references/RefMediaTest.java new file mode 100644 index 0000000000..1d976089a9 --- /dev/null +++ b/xabber/src/test/java/com/xabber/android/data/extension/references/RefMediaTest.java @@ -0,0 +1,34 @@ +package com.xabber.android.data.extension.references; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class RefMediaTest { + String xml; + RefFile.Builder builder; + RefMedia media; + + @Before + public void setUp() throws Exception { + builder = RefFile.newBuilder(); + builder.setMediaType("image/jpeg"); + builder.setName("summit.jpg"); + builder.setHeight(150); + builder.setWidth(160); + builder.setSize(3032449); + builder.setDesc("Photo from the summit."); + + media = new RefMedia(builder.build(), "https://upload02.xabber.org/5f7IyZ/guide.txt"); + + xml = "image/jpegsummit.jpg150" + + "1603032449Photo from the summit." + + "https://upload02.xabber.org/5f7IyZ/guide.txt"; + } + + @Test + public void toXML() { + assertEquals(xml, media.toXML().toString()); + } +} \ No newline at end of file diff --git a/xabber/src/test/java/com/xabber/android/data/extension/references/ReferencesManagerTest.java b/xabber/src/test/java/com/xabber/android/data/extension/references/ReferencesManagerTest.java new file mode 100644 index 0000000000..3c22df8ad9 --- /dev/null +++ b/xabber/src/test/java/com/xabber/android/data/extension/references/ReferencesManagerTest.java @@ -0,0 +1,145 @@ +package com.xabber.android.data.extension.references; + +import android.util.Pair; + +import com.xabber.android.data.TestApplication; + +import org.jivesoftware.smack.packet.Message; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(RobolectricTestRunner.class) +@Config(application = TestApplication.class) +public class ReferencesManagerTest { + + private String body1, body2, body3, body4, body5, body6, body7, body8; + private Message message1, message2, message3, message4, message5, message6, message7, message8; + + @Before + public void setUp() throws Exception { + body1 = "> Wednesday, June 5, 2019\n> [11:08:45] Валерий Миллер:\n> один\nдва"; + + message1 = new Message("test@jabber.com", body1); + message1.addExtension(new Forward(0, 70, null)); + + // ------- + + body2 = "https://upload02.xabber.org/5ff2744e91/iKIlTIyZ/guide.txt\nhello"; + + message2 = new Message("test@jabber.com", body2); + message2.addExtension(new Media(0, 57, null)); + + // ------- + + body3 = "Тест форматирования текста. Использование нескольких стилей."; + + message3 = new Message("test@jabber.com", body3); + message3.addExtension(new Markup(5, 18, true, true, false, false, null)); + message3.addExtension(new Markup(20, 25, false, true, false, false, null)); + message3.addExtension(new Markup(28, 40, false, false, true, false, null)); + message3.addExtension(new Markup(42, 51, false, false, false, true, null)); + + // ------- + + body4 = "> This is a quote\n" + + "> of two lines\n" + + "Hello world!"; + + message4 = new Message("test@jabber.com", body4); + message4.addExtension(new Quote(0, 37, "> ")); + + // ------- + + body5 = "Тест > форматирования текста. "; + + message5 = new Message("test@jabber.com", body5); + message5.addExtension(new Markup(10, 23, true, true, false, false, null)); + + // ------- + + body6 = ">> 😄😃😀 привет"; + + message6 = new Message("test@jabber.com", body6); + message6.addExtension(new Markup(13, 18, true, false, false, false, null)); + + // ------- + + body7 = "Тест форматирования текста"; + + message7 = new Message("test@jabber.com", body7); + message7.addExtension(new Markup(20, 26, false, false, false, false, "www.xabber.com")); + + // ------- + + body8 = "Пользователь, привет!"; + + message8 = new Message("test@jabber.com", body8); + message8.addExtension(new Mention(0, 11, "xmpp:test@jabber.com")); + } + + @Test + public void modifyBodyWithReferences1() { + Pair result = ReferencesManager.modifyBodyWithReferences(message1, body1); + assertEquals("два", result.first); + assertNull(result.second); + } + + @Test + public void modifyBodyWithReferences2() { + Pair result = ReferencesManager.modifyBodyWithReferences(message2, body2); + assertEquals("hello", result.first); + assertNull(result.second); + } + + @Test + public void modifyBodyWithReferences3() { + Pair result = ReferencesManager.modifyBodyWithReferences(message3, body3); + assertEquals("Тест форматирования текста. Использование нескольких стилей.", result.first); + assertEquals("Тест форматирования текста. " + + "Использование нескольких стилей.", result.second); + } + + @Test + public void modifyBodyWithReferences4() { + String expected = "\u2503 This is a quote\n" + + "\u2503 of two lines\n" + + "Hello world!"; + Pair result = ReferencesManager.modifyBodyWithReferences(message4, body4); + assertEquals(body4, result.first); + assertEquals(expected, result.second); + } + + @Test + public void modifyBodyWithReferences5() { + Pair result = ReferencesManager.modifyBodyWithReferences(message5, body5); + assertEquals("Тест > форматирования текста. ", result.first); + assertEquals("Тест > форматирования текста. <b>", result.second); + } + + @Test + public void modifyBodyWithReferences6() { + Pair result = ReferencesManager.modifyBodyWithReferences(message6, body6); + assertEquals(">> 😄😃😀 привет", result.first); + assertEquals(">> 😄😃😀 привет", result.second); + } + + @Test + public void modifyBodyWithReferences7() { + Pair result = ReferencesManager.modifyBodyWithReferences(message7, body7); + assertEquals("Тест форматирования текста", result.first); + assertEquals("Тест форматирования ‍текста", result.second); + } + + @Test + public void modifyBodyWithReferences8() { + Pair result = ReferencesManager.modifyBodyWithReferences(message8, body8); + assertEquals("Пользователь, привет!", result.first); + assertEquals("‍Пользователь, привет!", result.second); + } +} \ No newline at end of file diff --git a/xabber/src/test/java/com/xabber/android/data/extension/references/ReferencesProviderTest.java b/xabber/src/test/java/com/xabber/android/data/extension/references/ReferencesProviderTest.java new file mode 100644 index 0000000000..206d0b9b95 --- /dev/null +++ b/xabber/src/test/java/com/xabber/android/data/extension/references/ReferencesProviderTest.java @@ -0,0 +1,184 @@ +package com.xabber.android.data.extension.references; + +import com.xabber.android.data.TestApplication; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.StringReader; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(RobolectricTestRunner.class) +@Config(application = TestApplication.class) +public class ReferencesProviderTest { + + private ReferencesProvider provider; + private XmlPullParserFactory factory; + private String stringForward, stringMedia, stringMarkup1, + stringMarkup2, stringMention, stringQuote, stringNull, stringUnknown; + + @Before + public void setUp() throws Exception { + provider = new ReferencesProvider(); + factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + + stringForward = "" + + "" + + "" + + "" + + "hello" + + "" + + "" + + ""; + + stringMedia = "" + + "application/pdfAndroid-Architecture_1-1.pdf4255465" + + "https://upload02.xabber.org/5f70e738285c44c82039a73d42eccf27" + + "44e91/lQk6DkRJ/Android-Architecture_1-1.pdf" + + "image/pngScreenshot_20190414-194652.png251184" + + "1280720https://upload02.xabber.org/5f70" + + "e738285c44c82039a73d42eccf2744e91/rUdy3rHt/Screenshot_20190414-194652.png" + + ""; + + stringMarkup1 = "" + + ""; + + stringMarkup2 = "" + + "https://www.xabber.com"; + + stringMention = "" + + "xmpp:juliet@capulet.lit"; + + stringQuote = "" + + "> "; + + stringUnknown = ""; + stringNull = ""; + + } + + @Test + public void parse1() { + Forward element = (Forward) parseString(stringForward); + assertNotNull(element); + assertEquals("forward", element.getType().toString()); + assertEquals(11, element.getBegin()); + assertEquals(179, element.getEnd()); + assertEquals(1, element.getForwarded().size()); + } + + @Test + public void parse2() { + Media element = (Media) parseString(stringMedia); + assertNotNull(element); + assertEquals("media", element.getType().toString()); + assertEquals(0, element.getBegin()); + assertEquals(89, element.getEnd()); + assertEquals(2, element.getMedia().size()); + + RefMedia media1 = element.getMedia().get(0); + assertNotNull(media1); + assertEquals("https://upload02.xabber.org/5f70e738285c44c82039a73" + + "d42eccf2744e91/lQk6DkRJ/Android-Architecture_1-1.pdf", media1.getUri()); + + RefFile file1 = media1.getFile(); + assertNotNull(file1); + assertEquals("application/pdf", file1.getMediaType()); + assertEquals("Android-Architecture_1-1.pdf", file1.getName()); + assertEquals(4255465, file1.getSize()); + + RefMedia media2 = element.getMedia().get(1); + assertNotNull(media2); + assertEquals("https://upload02.xabber.org/5f70e738285c44c82039a73d42eccf274" + + "4e91/rUdy3rHt/Screenshot_20190414-194652.png", media2.getUri()); + + RefFile file2 = media2.getFile(); + assertNotNull(file2); + assertEquals("image/png", file2.getMediaType()); + assertEquals("Screenshot_20190414-194652.png", file2.getName()); + assertEquals(251184, file2.getSize()); + assertEquals(1280, file2.getHeight()); + assertEquals(720, file2.getWidth()); + } + + @Test + public void parse3() { + Markup element = (Markup) parseString(stringMarkup1); + assertNotNull(element); + assertEquals("markup", element.getType().toString()); + assertEquals(7, element.getBegin()); + assertEquals(10, element.getEnd()); + assertTrue(element.isBold()); + assertTrue(element.isItalic()); + assertFalse(element.isStrike()); + assertFalse(element.isUnderline()); + assertNull(element.getUri()); + } + + @Test + public void parse4() { + Markup element = (Markup) parseString(stringMarkup2); + assertNotNull(element); + assertEquals("markup", element.getType().toString()); + assertEquals(34, element.getBegin()); + assertEquals(37, element.getEnd()); + assertTrue(element.isBold()); + assertFalse(element.isItalic()); + assertFalse(element.isStrike()); + assertFalse(element.isUnderline()); + assertEquals("https://www.xabber.com", element.getUri()); + } + + @Test + public void parse5() { + Mention element = (Mention) parseString(stringMention); + assertNotNull(element); + assertEquals("mention", element.getType().toString()); + assertEquals(16, element.getBegin()); + assertEquals(22, element.getEnd()); + assertEquals("xmpp:juliet@capulet.lit", element.getUri()); + } + + @Test + public void parse6() { + Quote element = (Quote) parseString(stringQuote); + assertNotNull(element); + assertEquals("quote", element.getType().toString()); + assertEquals(0, element.getBegin()); + assertEquals(31, element.getEnd()); + assertEquals("> ", element.getMarker()); + } + + @Test + public void parse7() { + ReferenceElement element = parseString(stringUnknown); + assertNull(element); + + ReferenceElement element1 = parseString(stringNull); + assertNull(element1); + } + + private ReferenceElement parseString(String source) { + ReferenceElement result = null; + try { + XmlPullParser parser = factory.newPullParser(); + parser.setInput(new StringReader(source)); + result = provider.parse(parser, 0); + } catch (Exception e) { + fail("Exception while parsing: " + e.toString()); + } + return result; + } +} \ No newline at end of file