diff --git a/app/build.gradle b/app/build.gradle index 75321a6364..ce5ff0ff59 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "chat.rocket.android" minSdkVersion 21 targetSdkVersion versions.targetSdk - versionCode 2025 - versionName "2.3.1" + versionCode 2026 + versionName "2.3.2" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" multiDexEnabled true } diff --git a/app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt b/app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt index accb5663ab..b0513d6622 100644 --- a/app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt +++ b/app/src/main/java/chat/rocket/android/app/RocketChatApplication.kt @@ -16,13 +16,22 @@ import chat.rocket.android.app.migration.model.RealmBasedServerInfo import chat.rocket.android.app.migration.model.RealmPublicSetting import chat.rocket.android.app.migration.model.RealmSession import chat.rocket.android.app.migration.model.RealmUser -import chat.rocket.android.authentication.domain.model.toToken import chat.rocket.android.dagger.DaggerAppComponent import chat.rocket.android.dagger.qualifier.ForMessages import chat.rocket.android.helper.CrashlyticsTree import chat.rocket.android.infrastructure.LocalRepository -import chat.rocket.android.server.domain.* +import chat.rocket.android.infrastructure.installCrashlyticsWrapper +import chat.rocket.android.server.domain.AccountsRepository +import chat.rocket.android.server.domain.GetCurrentServerInteractor +import chat.rocket.android.server.domain.GetSettingsInteractor +import chat.rocket.android.server.domain.PublicSettings +import chat.rocket.android.server.domain.SITE_URL +import chat.rocket.android.server.domain.SaveCurrentServerInteractor +import chat.rocket.android.server.domain.SettingsRepository +import chat.rocket.android.server.domain.TokenRepository +import chat.rocket.android.server.domain.favicon import chat.rocket.android.server.domain.model.Account +import chat.rocket.android.server.domain.wideTile import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.serverLogoUrl import chat.rocket.android.widget.emoji.EmojiRepository @@ -34,13 +43,16 @@ import com.facebook.drawee.backends.pipeline.DraweeConfig import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.imagepipeline.core.ImagePipelineConfig import com.jakewharton.threetenabp.AndroidThreeTen -import dagger.android.* +import dagger.android.AndroidInjector +import dagger.android.DispatchingAndroidInjector +import dagger.android.HasActivityInjector +import dagger.android.HasBroadcastReceiverInjector +import dagger.android.HasServiceInjector import io.fabric.sdk.android.Fabric import io.realm.Realm import io.realm.RealmConfiguration import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.launch -import kotlinx.coroutines.experimental.runBlocking import timber.log.Timber import java.lang.ref.WeakReference import javax.inject.Inject @@ -69,7 +81,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje @Inject lateinit var getCurrentServerInteractor: GetCurrentServerInteractor @Inject - lateinit var multiServerRepository: MultiServerTokenRepository + lateinit var settingsInteractor: GetSettingsInteractor @Inject lateinit var settingsRepository: SettingsRepository @Inject @@ -81,8 +93,6 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje @Inject lateinit var prefs: SharedPreferences @Inject - lateinit var getAccountsInteractor: GetAccountsInteractor - @Inject lateinit var localRepository: LocalRepository @Inject @@ -101,8 +111,6 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje .lifecycle .addObserver(appLifecycleObserver) - // TODO - remove this on the future, temporary migration stuff for pre-release versions. - migrateInternalTokens() context = WeakReference(applicationContext) AndroidThreeTen.init(this) @@ -127,6 +135,29 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje } catch (ex: Exception) { Timber.d(ex, "Error migrating old accounts") } + + // TODO - remove this + checkCurrentServer() + } + + private fun checkCurrentServer() { + val currentServer = getCurrentServerInteractor.get() ?: "" + + if (currentServer == "") { + val message = "null currentServer" + Timber.d(IllegalStateException(message), message) + } + + val settings = settingsInteractor.get(currentServer) + if (settings.isEmpty()) { + val message = "Empty settings for: $currentServer" + Timber.d(IllegalStateException(message), message) + } + val baseUrl = settings[SITE_URL] + if (baseUrl == null) { + val message = "Server $currentServer SITE_URL" + Timber.d(IllegalStateException(message), message) + } } private fun migrateFromLegacy() { @@ -233,32 +264,13 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje } } - private fun migrateInternalTokens() { - if (!prefs.getBoolean(INTERNAL_TOKEN_MIGRATION_NEEDED, true)) { - Timber.d("Tokens already migrated") - return - } - - getCurrentServerInteractor.get()?.let { serverUrl -> - multiServerRepository.get(serverUrl)?.let { token -> - tokenRepository.save(serverUrl, Token(token.userId, token.authToken)) - } - } - - runBlocking { - getAccountsInteractor.get().forEach { account -> - multiServerRepository.get(account.serverUrl)?.let { token -> - tokenRepository.save(account.serverUrl, token.toToken()) - } - } - } - - prefs.edit { putBoolean(INTERNAL_TOKEN_MIGRATION_NEEDED, false) } - } - private fun setupCrashlytics() { val core = CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build() Fabric.with(this, Crashlytics.Builder().core(core).build()) + + installCrashlyticsWrapper(this@RocketChatApplication, + getCurrentServerInteractor, settingsInteractor, + accountRepository, localRepository) } private fun setupFresco() { @@ -301,6 +313,4 @@ private fun LocalRepository.hasMigrated() = getBoolean(LocalRepository.MIGRATION private fun LocalRepository.needOldMessagesCleanUp() = getBoolean(CLEANUP_OLD_MESSAGES_NEEDED, true) private fun LocalRepository.setOldMessagesCleanedUp() = save(CLEANUP_OLD_MESSAGES_NEEDED, false) -private const val INTERNAL_TOKEN_MIGRATION_NEEDED = "INTERNAL_TOKEN_MIGRATION_NEEDED" - private const val CLEANUP_OLD_MESSAGES_NEEDED = "CLEANUP_OLD_MESSAGES_NEEDED" \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt index 52483d5861..b413cfb81a 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt @@ -812,7 +812,7 @@ class ChatRoomPresenter @Inject constructor( } } if (typingStatusList.isNotEmpty()) { - view.showTypingStatus(typingStatusList) + view.showTypingStatus(typingStatusList.toList()) // copy typingStatusList } else { view.hideTypingStatusView() } diff --git a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt index 92e66474d5..eb34568210 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt @@ -31,7 +31,7 @@ interface ChatRoomView : LoadingView, MessageView { * * @param usernameList The list of username to show. */ - fun showTypingStatus(usernameList: ArrayList) + fun showTypingStatus(usernameList: List) /** * Hides the typing status view. diff --git a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt index 2d8bba2d56..86bdc57e6b 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt @@ -367,7 +367,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } } - override fun showTypingStatus(usernameList: ArrayList) { + override fun showTypingStatus(usernameList: List) { ui { when (usernameList.size) { 1 -> { diff --git a/app/src/main/java/chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt b/app/src/main/java/chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt index 144f545358..48fbb771da 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/viewmodel/ViewModelMapper.kt @@ -61,8 +61,8 @@ class ViewModelMapper @Inject constructor( ) { private val currentServer = serverInteractor.get()!! - private val settings: Map> = getSettingsInteractor.get(currentServer) - private val baseUrl = settings.baseUrl() + private val settings = getSettingsInteractor.get(currentServer) + private val baseUrl = currentServer private val token = tokenRepository.get(currentServer) private val currentUsername: String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY) private val secondaryTextColor = ContextCompat.getColor(context, R.color.colorSecondaryText) diff --git a/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt b/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt index fe5eefbf55..fd76f6d3f1 100644 --- a/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatrooms/ui/ChatRoomsFragment.kt @@ -3,6 +3,7 @@ package chat.rocket.android.chatrooms.ui import android.app.AlertDialog import android.os.Bundle import android.os.Handler +import android.renderscript.RSInvalidStateException import android.support.v4.app.Fragment import android.support.v7.app.AppCompatActivity import android.support.v7.util.DiffUtil @@ -37,11 +38,13 @@ import chat.rocket.android.widget.DividerItemDecoration import chat.rocket.common.model.RoomType import chat.rocket.core.internal.realtime.socket.model.State import chat.rocket.core.model.ChatRoom +import com.crashlytics.android.Crashlytics import dagger.android.support.AndroidSupportInjection import kotlinx.android.synthetic.main.fragment_chat_rooms.* import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.NonCancellable.isActive import timber.log.Timber +import java.security.InvalidParameterException import javax.inject.Inject private const val BUNDLE_CHAT_ROOM_ID = "BUNDLE_CHAT_ROOM_ID" diff --git a/app/src/main/java/chat/rocket/android/infrastructure/CrashlyticsWrapper.kt b/app/src/main/java/chat/rocket/android/infrastructure/CrashlyticsWrapper.kt new file mode 100644 index 0000000000..023d8412ce --- /dev/null +++ b/app/src/main/java/chat/rocket/android/infrastructure/CrashlyticsWrapper.kt @@ -0,0 +1,66 @@ +package chat.rocket.android.infrastructure + +import android.app.Application +import chat.rocket.android.BuildConfig +import chat.rocket.android.server.domain.AccountsRepository +import chat.rocket.android.server.domain.GetCurrentServerInteractor +import chat.rocket.android.server.domain.GetSettingsInteractor +import chat.rocket.android.server.domain.SITE_URL +import com.crashlytics.android.Crashlytics +import kotlinx.coroutines.experimental.runBlocking + +fun installCrashlyticsWrapper(context: Application, + currentServerInteractor: GetCurrentServerInteractor, + settingsInteractor: GetSettingsInteractor, + accountRepository: AccountsRepository, + localRepository: LocalRepository) { + if (isCrashlyticsEnabled()) { + Thread.setDefaultUncaughtExceptionHandler(RocketChatUncaughtExceptionHandler(currentServerInteractor, + settingsInteractor, accountRepository, localRepository)) + } +} + +private fun isCrashlyticsEnabled(): Boolean { + return !BuildConfig.DEBUG +} + +private class RocketChatUncaughtExceptionHandler( + val currentServerInteractor: GetCurrentServerInteractor, + val settingsInteractor: GetSettingsInteractor, + val accountRepository: AccountsRepository, + val localRepository: LocalRepository) + : Thread.UncaughtExceptionHandler { + + val crashlyticsHandler: Thread.UncaughtExceptionHandler? = Thread.getDefaultUncaughtExceptionHandler() + + override fun uncaughtException(t: Thread, e: Throwable) { + val currentServer = currentServerInteractor.get() ?: "" + Crashlytics.setString(KEY_CURRENT_SERVER, currentServer) + runBlocking { + val accounts = accountRepository.load() + Crashlytics.setString(KEY_ACCOUNTS, accounts.toString()) + } + + val settings = settingsInteractor.get(currentServer) + Crashlytics.setInt(KEY_SETTINGS_SIZE, settings.size) + val baseUrl = settings[SITE_URL]?.toString() + Crashlytics.setString(KEY_SETTINGS_BASE_URL, baseUrl) + + val user = localRepository.getCurrentUser(currentServer) + Crashlytics.setString(KEY_CURRENT_USER, user?.toString()) + Crashlytics.setString(KEY_CURRENT_USERNAME, localRepository.username()) + + if (crashlyticsHandler != null) { + crashlyticsHandler.uncaughtException(t, e) + } else { + throw RuntimeException("Missing default exception handler") + } + } +} + +private const val KEY_CURRENT_SERVER = "CURRENT_SERVER" +private const val KEY_CURRENT_USER = "CURRENT_USER" +private const val KEY_CURRENT_USERNAME = "CURRENT_USERNAME" +private const val KEY_ACCOUNTS = "ACCOUNTS" +private const val KEY_SETTINGS_SIZE = "SETTINGS_SIZE" +private const val KEY_SETTINGS_BASE_URL = "SETTINGS_BASE_URL" \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/server/domain/SettingsRepository.kt b/app/src/main/java/chat/rocket/android/server/domain/SettingsRepository.kt index ffd4911547..8e4fdc5228 100644 --- a/app/src/main/java/chat/rocket/android/server/domain/SettingsRepository.kt +++ b/app/src/main/java/chat/rocket/android/server/domain/SettingsRepository.kt @@ -5,11 +5,6 @@ import chat.rocket.core.model.Value typealias PublicSettings = Map> -interface SettingsRepository { - fun save(url: String, settings: PublicSettings) - fun get(url: String): PublicSettings -} - // Authentication methods. const val LDAP_ENABLE = "LDAP_Enable" const val CAS_ENABLE = "CAS_enabled" @@ -104,4 +99,9 @@ fun PublicSettings.uploadMaxFileSize(): Int { } fun PublicSettings.baseUrl(): String = this[SITE_URL]?.value as String -fun PublicSettings.siteName(): String? = this[SITE_NAME]?.value as String? \ No newline at end of file +fun PublicSettings.siteName(): String? = this[SITE_NAME]?.value as String? + +interface SettingsRepository { + fun save(url: String, settings: PublicSettings) + fun get(url: String): PublicSettings +} \ No newline at end of file diff --git a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesSettingsRepository.kt b/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesSettingsRepository.kt index 500096051c..9826b2d3fd 100644 --- a/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesSettingsRepository.kt +++ b/app/src/main/java/chat/rocket/android/server/infraestructure/SharedPreferencesSettingsRepository.kt @@ -5,6 +5,7 @@ import chat.rocket.android.infrastructure.LocalRepository.Companion.SETTINGS_KEY import chat.rocket.android.server.domain.PublicSettings import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.core.internal.SettingsAdapter +import timber.log.Timber class SharedPreferencesSettingsRepository( private val localRepository: LocalRepository @@ -13,11 +14,27 @@ class SharedPreferencesSettingsRepository( private val adapter = SettingsAdapter().lenient() override fun save(url: String, settings: PublicSettings) { + if (settings.isEmpty()) { + val message = "Saving empty settings for $SETTINGS_KEY$url" + Timber.d(IllegalStateException(message), message) + } localRepository.save("$SETTINGS_KEY$url", adapter.toJson(settings)) } override fun get(url: String): PublicSettings { - val settings = localRepository.get("$SETTINGS_KEY$url") - return if (settings == null) hashMapOf() else adapter.fromJson(settings) ?: hashMapOf() + val settingsStr = localRepository.get("$SETTINGS_KEY$url") + return if (settingsStr == null) { + val message = "NULL Settings for: $SETTINGS_KEY$url" + Timber.d(IllegalStateException(message), message) + hashMapOf() + } else { + val settings = adapter.fromJson(settingsStr) + + if (settings == null) { + val message = "NULL Settings for: $SETTINGS_KEY$url with saved settings: $settingsStr" + Timber.d(IllegalStateException(message), message) + } + settings ?: hashMapOf() + } } } \ No newline at end of file