diff --git a/CHANGES.md b/CHANGES.md index 477746fc7e..0f56b08aca 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ Features: Improvements: - Reduce default release build log level, and lab option to enable more logs. + - Display a no network indicator when there is no network (#559) Other changes: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/sync/SyncState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/sync/SyncState.kt index cc1c3f1a32..8c34e392b5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/sync/SyncState.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/sync/SyncState.kt @@ -22,4 +22,5 @@ sealed class SyncState { object PAUSED : SyncState() object KILLING : SyncState() object KILLED : SyncState() + object NO_NETWORK : SyncState() } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt index 412cb73c6e..ed1702ec07 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt @@ -18,12 +18,10 @@ package im.vector.matrix.android.internal.network import android.content.Context import com.novoda.merlin.Merlin -import com.novoda.merlin.MerlinsBeard import im.vector.matrix.android.internal.di.MatrixScope import timber.log.Timber import java.util.* import javax.inject.Inject -import kotlin.collections.ArrayList import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -35,29 +33,38 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context) .withDisconnectableCallbacks() .build(context) - private val merlinsBeard = MerlinsBeard.Builder().build(context) - private val listeners = Collections.synchronizedList(ArrayList()) + private val listeners = Collections.synchronizedSet(LinkedHashSet()) + + // True when internet is available + var hasInternetAccess = false + private set init { merlin.bind() merlin.registerDisconnectable { - Timber.v("On Disconnect") - val localListeners = listeners.toList() - localListeners.forEach { - it.onDisconnect() + if (hasInternetAccess) { + Timber.v("On Disconnect") + hasInternetAccess = false + val localListeners = listeners.toList() + localListeners.forEach { + it.onDisconnect() + } } } merlin.registerConnectable { - Timber.v("On Connect") - val localListeners = listeners.toList() - localListeners.forEach { - it.onConnect() + if (!hasInternetAccess) { + Timber.v("On Connect") + hasInternetAccess = true + val localListeners = listeners.toList() + localListeners.forEach { + it.onConnect() + } } } } suspend fun waitUntilConnected() { - if (isConnected()) { + if (hasInternetAccess) { return } else { suspendCoroutine { continuation -> @@ -79,10 +86,6 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context) listeners.remove(listener) } - fun isConnected(): Boolean { - return merlinsBeard.isConnected - } - interface Listener { fun onConnect() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt index 148e25b3a7..b5d83607b2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt @@ -93,8 +93,8 @@ open class SyncService : Service() { } fun doSync(once: Boolean = false) { - if (!networkConnectivityChecker.isConnected()) { - Timber.v("Sync is Paused. Waiting...") + if (!networkConnectivityChecker.hasInternetAccess) { + Timber.v("No internet access. Waiting...") //TODO Retry in ? timer.schedule(object : TimerTask() { override fun run() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index f6ff11c1fe..1cb6575161 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt @@ -50,6 +50,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, private val lock = Object() private var cancelableTask: Cancelable? = null + private var isStarted = false + init { updateStateTo(SyncState.IDLE) } @@ -60,19 +62,18 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, } fun restart() = synchronized(lock) { - if (state is SyncState.PAUSED) { + if (!isStarted) { Timber.v("Resume sync...") - updateStateTo(SyncState.RUNNING(afterPause = true)) + isStarted = true lock.notify() } } fun pause() = synchronized(lock) { - if (state is SyncState.RUNNING) { + if (isStarted) { Timber.v("Pause sync...") - updateStateTo(SyncState.PAUSED) + isStarted = false cancelableTask?.cancel() - lock.notify() } } @@ -87,19 +88,31 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, return liveState } + override fun onConnect() { + Timber.v("Network is back") + synchronized(lock) { + lock.notify() + } + } + override fun run() { Timber.v("Start syncing...") + isStarted = true networkConnectivityChecker.register(this) backgroundDetectionObserver.register(this) while (state != SyncState.KILLING) { Timber.v("Entering loop, state: $state") - if (!networkConnectivityChecker.isConnected() || state == SyncState.PAUSED) { - Timber.v("No network or sync is Paused. Waiting...") - synchronized(lock) { - lock.wait() - } + if (!networkConnectivityChecker.hasInternetAccess) { + Timber.v("No network. Waiting...") + updateStateTo(SyncState.NO_NETWORK) + synchronized(lock) { lock.wait() } + Timber.v("...unlocked") + } else if (!isStarted) { + Timber.v("Sync is Paused. Waiting...") + updateStateTo(SyncState.PAUSED) + synchronized(lock) { lock.wait() } Timber.v("...unlocked") } else { if (state !is SyncState.RUNNING) { @@ -167,16 +180,11 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, } private fun updateStateTo(newState: SyncState) { - Timber.v("Update state to $newState") + Timber.v("Update state from $state to $newState") state = newState liveState.postValue(newState) } - override fun onConnect() { - synchronized(lock) { - lock.notify() - } - } override fun onMoveToForeground() { restart() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/BackgroundDetectionObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/BackgroundDetectionObserver.kt index a426bdd084..9318b10717 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/BackgroundDetectionObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/BackgroundDetectionObserver.kt @@ -33,7 +33,7 @@ internal class BackgroundDetectionObserver @Inject constructor() : LifecycleObse private set private - val listeners = ArrayList() + val listeners = LinkedHashSet() fun register(listener: Listener) { listeners.add(listener) diff --git a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml index 134b699a7a..a588bb36fd 100644 --- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml +++ b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml @@ -3,4 +3,7 @@ + + + There is no network connection right now \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index bd4b2ca4df..acfac104d4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -19,7 +19,6 @@ package im.vector.riotx.features.home import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater -import android.view.View import androidx.core.view.forEachIndexed import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders @@ -30,7 +29,6 @@ import com.google.android.material.bottomnavigation.BottomNavigationItemView import com.google.android.material.bottomnavigation.BottomNavigationMenuView import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState -import im.vector.matrix.android.api.session.sync.SyncState import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.platform.ToolbarConfigurable @@ -208,11 +206,7 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate { unreadCounterBadgeViews[INDEX_CATCHUP].render(UnreadCounterBadgeView.State(it.notificationCountCatchup, it.notificationHighlightCatchup)) unreadCounterBadgeViews[INDEX_PEOPLE].render(UnreadCounterBadgeView.State(it.notificationCountPeople, it.notificationHighlightPeople)) unreadCounterBadgeViews[INDEX_ROOMS].render(UnreadCounterBadgeView.State(it.notificationCountRooms, it.notificationHighlightRooms)) - syncProgressBar.visibility = when (it.syncState) { - is SyncState.RUNNING -> if (it.syncState.afterPause) View.VISIBLE else View.GONE - else -> View.GONE - } - syncProgressBarWrap.visibility = syncProgressBar.visibility + syncStateView.render(it.syncState) } companion object { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 19262fad49..cd1ccb01d2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -65,7 +65,6 @@ import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent -import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent @@ -77,8 +76,8 @@ import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.files.addEntryToDownloadManager import im.vector.riotx.core.glide.GlideApp -import im.vector.riotx.core.ui.views.NotificationAreaView import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.core.ui.views.NotificationAreaView import im.vector.riotx.core.utils.* import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy @@ -251,11 +250,7 @@ class RoomDetailFragment : } roomDetailViewModel.selectSubscribe(RoomDetailViewState::syncState) { syncState -> - syncProgressBar.visibility = when (syncState) { - is SyncState.RUNNING -> if (syncState.afterPause) View.VISIBLE else View.GONE - else -> View.GONE - } - syncProgressBarWrap.visibility = syncProgressBar.visibility + syncStateView.render(syncState) } } diff --git a/vector/src/main/java/im/vector/riotx/features/sync/widget/SyncStateView.kt b/vector/src/main/java/im/vector/riotx/features/sync/widget/SyncStateView.kt new file mode 100755 index 0000000000..43158f4e86 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/sync/widget/SyncStateView.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.sync.widget + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.FrameLayout +import androidx.core.view.isVisible +import im.vector.matrix.android.api.session.sync.SyncState +import im.vector.riotx.R +import kotlinx.android.synthetic.main.view_sync_state.view.* + +class SyncStateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) + : FrameLayout(context, attrs, defStyle) { + + init { + View.inflate(context, R.layout.view_sync_state, this) + } + + + fun render(newState: SyncState) { + syncStateProgressBar.visibility = when (newState) { + is SyncState.RUNNING -> if (newState.afterPause) View.VISIBLE else View.GONE + else -> View.GONE + } + syncStateNoNetwork.isVisible = newState == SyncState.NO_NETWORK + } +} diff --git a/vector/src/main/res/layout/fragment_home_detail.xml b/vector/src/main/res/layout/fragment_home_detail.xml index 13d3fea803..8bc0603013 100644 --- a/vector/src/main/res/layout/fragment_home_detail.xml +++ b/vector/src/main/res/layout/fragment_home_detail.xml @@ -44,29 +44,13 @@ - - - - - - + app:layout_constraintTop_toBottomOf="@id/groupToolbar" /> + app:layout_constraintTop_toBottomOf="@id/syncStateView" /> - - - - - + app:layout_constraintTop_toBottomOf="@id/roomToolbar" /> + + + + + + + + + + + \ No newline at end of file