From 3e6b65e174bb282df0c76c658c8adec622028e67 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Sep 2019 18:21:56 +0200 Subject: [PATCH] Handle M_CONSENT_NOT_GIVEN error (#64) --- CHANGES.md | 2 +- matrix-sdk-android/build.gradle | 3 + .../api/failure/ConsentNotGivenError.kt | 22 +++ .../android/internal/network/Request.kt | 7 + vector/build.gradle | 3 + vector/src/main/AndroidManifest.xml | 1 + .../src/main/assets/open_source_licenses.html | 5 + .../vector/riotx/core/dialogs/DialogLocker.kt | 4 +- .../vector/riotx/core/error/ErrorFormatter.kt | 10 +- .../riotx/core/platform/VectorBaseActivity.kt | 32 ++++- .../riotx/core/utils/WeakReferenceDelegate.kt | 32 +++++ .../features/consent/ConsentNotGivenHelper.kt | 57 ++++++++ .../webview/ConsentWebViewEventListener.kt | 88 ++++++++++++ .../webview/DefaultWebViewEventListener.kt | 48 +++++++ .../features/webview/VectorWebViewActivity.kt | 133 ++++++++++++++++++ .../features/webview/VectorWebViewClient.kt | 83 +++++++++++ .../features/webview/WebViewEventListener.kt | 59 ++++++++ .../webview/WebViewEventListenerFactory.kt | 29 ++++ .../riotx/features/webview/WebViewMode.kt | 38 +++++ .../res/layout/activity_vector_web_view.xml | 36 +++++ vector/src/main/res/values/strings_riotX.xml | 3 + 21 files changed, 689 insertions(+), 6 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/ConsentNotGivenError.kt create mode 100644 vector/src/main/java/im/vector/riotx/core/utils/WeakReferenceDelegate.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/consent/ConsentNotGivenHelper.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/webview/ConsentWebViewEventListener.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/webview/DefaultWebViewEventListener.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/webview/VectorWebViewActivity.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/webview/VectorWebViewClient.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/webview/WebViewEventListener.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/webview/WebViewEventListenerFactory.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/webview/WebViewMode.kt create mode 100644 vector/src/main/res/layout/activity_vector_web_view.xml diff --git a/CHANGES.md b/CHANGES.md index 8769b11ea7..2b07b6d769 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in RiotX 0.5.0 (2019-XX-XX) =================================================== Features: - - + - Handle M_CONSENT_NOT_GIVEN error (#64) Improvements: - Reduce default release build log level, and lab option to enable more logs. diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index fbe0969159..8002625e12 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -139,6 +139,9 @@ dependencies { implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0' + // Bus + implementation 'org.greenrobot:eventbus:3.1.1' + debugImplementation 'com.airbnb.okreplay:okreplay:1.4.0' releaseImplementation 'com.airbnb.okreplay:noop:1.4.0' androidTestImplementation 'com.airbnb.okreplay:espresso:1.4.0' diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/ConsentNotGivenError.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/ConsentNotGivenError.kt new file mode 100644 index 0000000000..c780720a18 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/ConsentNotGivenError.kt @@ -0,0 +1,22 @@ +/* + * 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.matrix.android.api.failure + +// This data class will be sent to the bus +data class ConsentNotGivenError( + val consentUri: String +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt index 3d1e433b1b..4be2d4a27f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt @@ -18,10 +18,12 @@ package im.vector.matrix.android.internal.network import com.squareup.moshi.JsonDataException import com.squareup.moshi.Moshi +import im.vector.matrix.android.api.failure.ConsentNotGivenError import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.internal.di.MoshiProvider import okhttp3.ResponseBody +import org.greenrobot.eventbus.EventBus import retrofit2.Call import timber.log.Timber import java.io.IOException @@ -65,6 +67,11 @@ internal class Request { val matrixError = matrixErrorAdapter.fromJson(errorBodyStr) if (matrixError != null) { + if (matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank()) { + // Also send this error to the bus, for a global management + EventBus.getDefault().post(ConsentNotGivenError(matrixError.consentUri)) + } + return Failure.ServerError(matrixError, httpCode) } } catch (ex: JsonDataException) { diff --git a/vector/build.gradle b/vector/build.gradle index de47937676..866a95b740 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -280,6 +280,9 @@ dependencies { implementation "ru.noties.markwon:html:$markwon_version" implementation 'me.saket:better-link-movement-method:2.2.0' + // Bus + implementation 'org.greenrobot:eventbus:3.1.1' + // Passphrase strength helper implementation 'com.nulab-inc:zxcvbn:1.2.5' diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index e4cdaee2e4..949da5132f 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -65,6 +65,7 @@ + diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index 9e87d466af..1195d38b90 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -349,6 +349,11 @@ SOFTWARE.
Copyright 2018 The diff-match-patch Authors. https://github.com/google/diff-match-patch +
  • + EventBus +
    + Copyright (C) 2012-2017 Markus Junginger, greenrobot (http://greenrobot.org) +
  • diff --git a/vector/src/main/java/im/vector/riotx/core/dialogs/DialogLocker.kt b/vector/src/main/java/im/vector/riotx/core/dialogs/DialogLocker.kt index 5418e2270a..60397fb6b6 100644 --- a/vector/src/main/java/im/vector/riotx/core/dialogs/DialogLocker.kt +++ b/vector/src/main/java/im/vector/riotx/core/dialogs/DialogLocker.kt @@ -26,9 +26,9 @@ private const val KEY_DIALOG_IS_DISPLAYED = "DialogLocker.KEY_DIALOG_IS_DISPLAYE /** * Class to avoid displaying twice the same dialog */ -class DialogLocker() : Restorable { +class DialogLocker(savedInstanceState: Bundle?) : Restorable { - private var isDialogDisplayed: Boolean = false + private var isDialogDisplayed = savedInstanceState?.getBoolean(KEY_DIALOG_IS_DISPLAYED, false) == true private fun unlock() { isDialogDisplayed = false diff --git a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt index d42bce64c5..bb7892e109 100644 --- a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt @@ -17,6 +17,7 @@ package im.vector.riotx.core.error import im.vector.matrix.android.api.failure.Failure +import im.vector.matrix.android.api.failure.MatrixError import im.vector.riotx.R import im.vector.riotx.core.resources.StringProvider import javax.inject.Inject @@ -34,8 +35,13 @@ class ErrorFormatter @Inject constructor(val stringProvider: StringProvider) { null -> null is Failure.NetworkConnection -> stringProvider.getString(R.string.error_no_network) is Failure.ServerError -> { - throwable.error.message.takeIf { it.isNotEmpty() } - ?: throwable.error.code.takeIf { it.isNotEmpty() } + if (throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN) { + // Special case for terms and conditions + stringProvider.getString(R.string.error_terms_not_accepted) + } else { + throwable.error.message.takeIf { it.isNotEmpty() } + ?: throwable.error.code.takeIf { it.isNotEmpty() } + } } else -> throwable.localizedMessage } diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt index fad2c0ed87..a0e67d51ca 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt @@ -36,11 +36,14 @@ import butterknife.Unbinder import com.airbnb.mvrx.BaseMvRxActivity import com.bumptech.glide.util.Util import com.google.android.material.snackbar.Snackbar +import im.vector.matrix.android.api.failure.ConsentNotGivenError import im.vector.riotx.BuildConfig import im.vector.riotx.R import im.vector.riotx.core.di.* +import im.vector.riotx.core.dialogs.DialogLocker import im.vector.riotx.core.utils.toast import im.vector.riotx.features.configuration.VectorConfiguration +import im.vector.riotx.features.consent.ConsentNotGivenHelper import im.vector.riotx.features.navigation.Navigator import im.vector.riotx.features.rageshake.BugReportActivity import im.vector.riotx.features.rageshake.BugReporter @@ -50,6 +53,9 @@ import im.vector.riotx.features.themes.ThemeUtils import im.vector.riotx.receivers.DebugReceiver import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode import timber.log.Timber import kotlin.system.measureTimeMillis @@ -391,6 +397,31 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector { } } + /* ========================================================================================== + * User Consent + * ========================================================================================== */ + + private val consentNotGivenHelper by lazy { + ConsentNotGivenHelper(this, DialogLocker(savedInstanceState)) + .apply { restorables.add(this) } + } + + override fun onStart() { + super.onStart() + EventBus.getDefault().register(this) + } + + override fun onStop() { + super.onStop() + EventBus.getDefault().unregister(this) + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onConsentNotGivenError(consentNotGivenError: ConsentNotGivenError) { + consentNotGivenHelper.displayDialog(consentNotGivenError.consentUri, + screenComponent.session().sessionParams.homeServerConnectionConfig.homeServerUri.host ?: "") + } + /* ========================================================================================== * Temporary method * ========================================================================================== */ @@ -402,5 +433,4 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector { toast(getString(R.string.not_implemented)) } } - } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/core/utils/WeakReferenceDelegate.kt b/vector/src/main/java/im/vector/riotx/core/utils/WeakReferenceDelegate.kt new file mode 100644 index 0000000000..d892d64327 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/utils/WeakReferenceDelegate.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2018 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.core.utils + +import java.lang.ref.WeakReference +import kotlin.reflect.KProperty + +fun weak(value: T) = WeakReferenceDelegate(value) + +class WeakReferenceDelegate(value: T) { + + private var weakReference: WeakReference = WeakReference(value) + + operator fun getValue(thisRef: Any, property: KProperty<*>): T? = weakReference.get() + operator fun setValue(thisRef: Any, property: KProperty<*>, value: T) { + weakReference = WeakReference(value) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/consent/ConsentNotGivenHelper.kt b/vector/src/main/java/im/vector/riotx/features/consent/ConsentNotGivenHelper.kt new file mode 100644 index 0000000000..0108e0cd88 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/consent/ConsentNotGivenHelper.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2018 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.consent + +import android.app.Activity +import androidx.appcompat.app.AlertDialog +import im.vector.riotx.R +import im.vector.riotx.core.dialogs.DialogLocker +import im.vector.riotx.core.platform.Restorable +import im.vector.riotx.features.webview.VectorWebViewActivity +import im.vector.riotx.features.webview.WebViewMode + +class ConsentNotGivenHelper(private val activity: Activity, + private val dialogLocker: DialogLocker) : + Restorable by dialogLocker { + + /* ========================================================================================== + * Public methods + * ========================================================================================== */ + + /** + * Display the consent dialog, if not already displayed + */ + fun displayDialog(consentUri: String, homeServerHost: String) { + dialogLocker.displayDialog { + AlertDialog.Builder(activity) + .setTitle(R.string.settings_app_term_conditions) + .setMessage(activity.getString(R.string.dialog_user_consent_content, homeServerHost)) + .setPositiveButton(R.string.dialog_user_consent_submit) { _, _ -> + openWebViewActivity(consentUri) + } + } + } + + /* ========================================================================================== + * Private + * ========================================================================================== */ + + private fun openWebViewActivity(consentUri: String) { + val intent = VectorWebViewActivity.getIntent(activity, consentUri, activity.getString(R.string.settings_app_term_conditions), WebViewMode.CONSENT) + activity.startActivity(intent) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/webview/ConsentWebViewEventListener.kt b/vector/src/main/java/im/vector/riotx/features/webview/ConsentWebViewEventListener.kt new file mode 100644 index 0000000000..c09639c29f --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/webview/ConsentWebViewEventListener.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2018 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.webview + +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.Session +import im.vector.riotx.core.platform.VectorBaseActivity +import im.vector.riotx.core.utils.weak +import timber.log.Timber + +private const val SUCCESS_URL_SUFFIX = "/_matrix/consent" +private const val RIOT_BOT_ID = "@riot-bot:matrix.org" + +/** + * This class is the Consent implementation of WebViewEventListener. + * It is used to manage the consent agreement flow. + */ +class ConsentWebViewEventListener(activity: VectorBaseActivity, + private val session: Session, + private val delegate: WebViewEventListener) + : WebViewEventListener by delegate { + + private val safeActivity: VectorBaseActivity? by weak(activity) + + override fun onPageFinished(url: String) { + delegate.onPageFinished(url) + if (url.endsWith(SUCCESS_URL_SUFFIX)) { + createRiotBotRoomIfNeeded() + } + } + + /** + * This methods try to create the RiotBot room when the user gives his agreement + */ + private fun createRiotBotRoomIfNeeded() { + safeActivity?.let { + /* We do not create a Room with RiotBot in RiotX for the moment + val joinedRooms = session.dataHandler.store.rooms.filter { + it.isJoined + } + if (joinedRooms.isEmpty()) { + it.showWaitingView() + // Ensure we can create a Room with riot-bot. Error can be a MatrixError: "Federation denied with matrix.org.", or any other error. + session.profileApiClient + .displayname(RIOT_BOT_ID, object : MatrixCallback(createRiotBotRoomCallback) { + override fun onSuccess(info: String?) { + // Ok, the Home Server knows riot-Bot, so create a Room with him + session.createDirectMessageRoom(RIOT_BOT_ID, createRiotBotRoomCallback) + } + }) + } else { + */ + it.finish() + /* + } + */ + } + } + + /** + * APICallback instance + */ + private val createRiotBotRoomCallback = object : MatrixCallback { + override fun onSuccess(data: String) { + Timber.d("## On success : succeed to invite riot-bot") + safeActivity?.finish() + } + + override fun onFailure(failure: Throwable) { + Timber.e("## On error : failed to invite riot-bot $failure") + safeActivity?.finish() + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/webview/DefaultWebViewEventListener.kt b/vector/src/main/java/im/vector/riotx/features/webview/DefaultWebViewEventListener.kt new file mode 100644 index 0000000000..f72d719872 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/webview/DefaultWebViewEventListener.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2018 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.webview + +import timber.log.Timber + +/** + * This class is the default implementation of WebViewEventListener. + * It can be used with delegation pattern + */ + +class DefaultWebViewEventListener : WebViewEventListener { + + override fun pageWillStart(url: String) { + Timber.v("On page will start: $url") + } + + override fun onPageStarted(url: String) { + Timber.d("On page started: $url") + } + + override fun onPageFinished(url: String) { + Timber.d("On page finished: $url") + } + + override fun onPageError(url: String, errorCode: Int, description: String) { + Timber.e("On received error: $url - errorCode: $errorCode - message: $description") + } + + override fun shouldOverrideUrlLoading(url: String): Boolean { + Timber.v("Should override url: $url") + return false + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/webview/VectorWebViewActivity.kt b/vector/src/main/java/im/vector/riotx/features/webview/VectorWebViewActivity.kt new file mode 100644 index 0000000000..914de2261f --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/webview/VectorWebViewActivity.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2018 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.webview + +import android.content.Context +import android.content.Intent +import android.os.Build +import android.webkit.WebChromeClient +import android.webkit.WebView +import androidx.annotation.CallSuper +import im.vector.matrix.android.api.session.Session +import im.vector.riotx.R +import im.vector.riotx.core.di.ScreenComponent +import im.vector.riotx.core.platform.VectorBaseActivity +import kotlinx.android.synthetic.main.activity_vector_web_view.* +import javax.inject.Inject + +/** + * This class is responsible for managing a WebView + * It does also have a loading view and a toolbar + * It relies on the VectorWebViewClient + * This class shouldn't be extended. To add new behaviors, you might create a new WebViewMode and a new WebViewEventListener + */ +class VectorWebViewActivity : VectorBaseActivity() { + + override fun getLayoutRes() = R.layout.activity_vector_web_view + + @Inject lateinit var session: Session + + @CallSuper + override fun injectWith(injector: ScreenComponent) { + session = injector.session() + } + + override fun initUiAndData() { + configureToolbar(webview_toolbar) + waitingView = findViewById(R.id.simple_webview_loader) + + simple_webview.settings.apply { + // Enable Javascript + javaScriptEnabled = true + + // Use WideViewport and Zoom out if there is no viewport defined + useWideViewPort = true + loadWithOverviewMode = true + + // Enable pinch to zoom without the zoom buttons + builtInZoomControls = true + + // Allow use of Local Storage + domStorageEnabled = true + + allowFileAccessFromFileURLs = true + allowUniversalAccessFromFileURLs = true + + displayZoomControls = false + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val cookieManager = android.webkit.CookieManager.getInstance() + cookieManager.setAcceptThirdPartyCookies(simple_webview, true) + } + + val url = intent.extras.getString(EXTRA_URL) + val title = intent.extras.getString(EXTRA_TITLE, USE_TITLE_FROM_WEB_PAGE) + if (title != USE_TITLE_FROM_WEB_PAGE) { + setTitle(title) + } + + val webViewMode = intent.extras.getSerializable(EXTRA_MODE) as WebViewMode + val eventListener = webViewMode.eventListener(this, session) + simple_webview.webViewClient = VectorWebViewClient(eventListener) + simple_webview.webChromeClient = object : WebChromeClient() { + override fun onReceivedTitle(view: WebView, title: String) { + if (title == USE_TITLE_FROM_WEB_PAGE) { + setTitle(title) + } + } + } + simple_webview.loadUrl(url) + } + + /* ========================================================================================== + * UI event + * ========================================================================================== */ + + override fun onBackPressed() { + if (simple_webview.canGoBack()) { + simple_webview.goBack() + } else { + super.onBackPressed() + } + } + + /* ========================================================================================== + * Companion + * ========================================================================================== */ + + companion object { + private const val EXTRA_URL = "EXTRA_URL" + private const val EXTRA_TITLE = "EXTRA_TITLE" + private const val EXTRA_MODE = "EXTRA_MODE" + + private const val USE_TITLE_FROM_WEB_PAGE = "" + + fun getIntent(context: Context, + url: String, + title: String = USE_TITLE_FROM_WEB_PAGE, + mode: WebViewMode = WebViewMode.DEFAULT): Intent { + return Intent(context, VectorWebViewActivity::class.java) + .apply { + putExtra(EXTRA_URL, url) + putExtra(EXTRA_TITLE, title) + putExtra(EXTRA_MODE, mode) + } + } + } +} + diff --git a/vector/src/main/java/im/vector/riotx/features/webview/VectorWebViewClient.kt b/vector/src/main/java/im/vector/riotx/features/webview/VectorWebViewClient.kt new file mode 100644 index 0000000000..080bbacae8 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/webview/VectorWebViewClient.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2018 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.webview + +import android.annotation.TargetApi +import android.graphics.Bitmap +import android.os.Build +import android.webkit.WebResourceError +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient + +/** + * This class inherits from WebViewClient. It has to be used with a WebView. + * It's responsible for dispatching events to the WebViewEventListener + */ +class VectorWebViewClient(private val eventListener: WebViewEventListener) : WebViewClient() { + + private var mInError: Boolean = false + + @TargetApi(Build.VERSION_CODES.N) + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + return shouldOverrideUrl(request.url.toString()) + } + + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + return shouldOverrideUrl(url) + } + + override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + mInError = false + eventListener.onPageStarted(url) + } + + override fun onPageFinished(view: WebView, url: String) { + super.onPageFinished(view, url) + if (!mInError) { + eventListener.onPageFinished(url) + } + } + + override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { + super.onReceivedError(view, errorCode, description, failingUrl) + if (!mInError) { + mInError = true + eventListener.onPageError(failingUrl, errorCode, description) + } + } + + @TargetApi(Build.VERSION_CODES.N) + override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceError) { + super.onReceivedError(view, request, error) + if (!mInError) { + mInError = true + eventListener.onPageError(request.url.toString(), error.errorCode, error.description.toString()) + } + } + + private fun shouldOverrideUrl(url: String): Boolean { + mInError = false + val shouldOverrideUrlLoading = eventListener.shouldOverrideUrlLoading(url) + if (!shouldOverrideUrlLoading) { + eventListener.pageWillStart(url) + } + return shouldOverrideUrlLoading + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/webview/WebViewEventListener.kt b/vector/src/main/java/im/vector/riotx/features/webview/WebViewEventListener.kt new file mode 100644 index 0000000000..571eeff4fe --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/webview/WebViewEventListener.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2018 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.webview + +interface WebViewEventListener { + + /** + * Triggered when a webview page is about to be started. + * + * @param url The url about to be rendered. + */ + fun pageWillStart(url: String) + + /** + * Triggered when a loading webview page has started. + * + * @param url The rendering url. + */ + fun onPageStarted(url: String) + + /** + * Triggered when a loading webview page has finished loading but has not been rendered yet. + * + * @param url The finished url. + */ + fun onPageFinished(url: String) + + /** + * Triggered when an error occurred while loading a page. + * + * @param url The url that failed. + * @param errorCode The error code. + * @param description The error description. + */ + fun onPageError(url: String, errorCode: Int, description: String) + + /** + * Triggered when a webview load an url + * + * @param url The url about to be rendered. + * @return true if the method needs to manage some custom handling + */ + fun shouldOverrideUrlLoading(url: String): Boolean + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/webview/WebViewEventListenerFactory.kt b/vector/src/main/java/im/vector/riotx/features/webview/WebViewEventListenerFactory.kt new file mode 100644 index 0000000000..d84b72a49d --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/webview/WebViewEventListenerFactory.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2018 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.webview + +import im.vector.matrix.android.api.session.Session +import im.vector.riotx.core.platform.VectorBaseActivity + +interface WebViewEventListenerFactory { + + /** + * @return an instance of WebViewEventListener + */ + fun eventListener(activity: VectorBaseActivity, session: Session): WebViewEventListener + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/webview/WebViewMode.kt b/vector/src/main/java/im/vector/riotx/features/webview/WebViewMode.kt new file mode 100644 index 0000000000..86e9a2f18b --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/webview/WebViewMode.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2018 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.webview + +import im.vector.matrix.android.api.session.Session +import im.vector.riotx.core.platform.VectorBaseActivity + +/** + * This enum indicates the WebView mode. It's responsible for creating a WebViewEventListener + */ +enum class WebViewMode : WebViewEventListenerFactory { + + DEFAULT { + override fun eventListener(activity: VectorBaseActivity, session: Session): WebViewEventListener { + return DefaultWebViewEventListener() + } + }, + CONSENT { + override fun eventListener(activity: VectorBaseActivity, session: Session): WebViewEventListener { + return ConsentWebViewEventListener(activity, session, DefaultWebViewEventListener()) + } + }; + +} \ No newline at end of file diff --git a/vector/src/main/res/layout/activity_vector_web_view.xml b/vector/src/main/res/layout/activity_vector_web_view.xml new file mode 100644 index 0000000000..8a5633acbc --- /dev/null +++ b/vector/src/main/res/layout/activity_vector_web_view.xml @@ -0,0 +1,36 @@ + + + + + + + + + + diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 3dd8c6b36a..0ef32154b7 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -5,4 +5,7 @@ Enable verbose logs. Verbose logs will help developers by providing more logs when you send a RageShake. Even when enabled, the application does not log message contents or any other private data. + + Please retry once you have accepted the terms and conditions of your homeserver. + \ No newline at end of file